Теперь, когда вы знаете о ключевых словах self и super и переменных экземпляра, мы можем вернуться к теме, которую я беспечно пропустил ранее.

Я описал, как инициализировать новоиспеченные экземпляры путем вызова инициализаторов, и подчеркнул, что вы всегда должны делать это, но при этом ничего не сказал о том, как написать инициализатор своего собственного класса. Создавая свой класс, вы, вероятно, захотите обеспечить удобный инициализатор, функциональность которого выходит за рамки функциональности наследуемых инициализаторов. Чаще всего ваша цель будет заключаться в том, чтобы принять некоторые параметры и использовать их для задания начальных значений некоторых переменных экземпляра.

 

Войны из-за точки

Естественно, по поводу синтаксиса свойств и записи с точкой разгораются целые религиозные войны и крестовые походы. Одна сторона утверждает, что синтаксис свойств удобный и компактный, и делает язык Objective-C больше похожим на другие языки с подобной записью. Другая сторона возражает, что этот синтаксис слишком ограничен.

Например, противники синтаксиса свойств говорят, что класс UlScrollview имеет свойство contentview, но, устанавливая его, вы скорее всего захотите одновременно анимировать прокрутку, что можно сделать с помощью вызова setContentView: animated:. Это своего рода метод доступа, но он принимает два параметра. Выразить такое с помощью синтаксиса свойств не получится, так что вам придется вернуться к явному вызову метода. В результате синтаксис свойств не дает никакого выигрыша и скорее в состоянии ввести в заблуждение (вы можете просто забыть добавить анимацию).

Другим возражением против синтаксиса свойств является то, что компилятор ограничивает его использование. Например, можно использовать формальный вызов метода для отправки сообщения number экземпляру Dog, типизированному как id, но добавить свойство number с записью с помощью точки в такой экземпляр невозможно.

Еще одной проблемой является то, что возможно ошибочно использовать синтаксис свойств для вызова метода, который, строго говоря, методом получения значения переменной экземпляра не является. Например, метод lastObject класса NSArray — это просто метод; его нет в списке свойств. Тем не менее программисты часто пишут myArray. lastObj ect только потому, что написать это проще и быстрее, чем [myArray lastObject], и еще потому, что этот способ работает. Но опять же так нельзя поступить с методами, которые не возвращают значение или принимают какие-либо параметры. Соответствующий метод должен выглядеть как метод доступа для получения значения, даже если в действительности он им не является. Такое неправомерное использование синтаксиса свойств выглядит отвратительно, но одновременно столь заманчиво, что даже встречается в примерах исходных текстов Apple.

 

Пусть, например, в случае класса Dog с переменной экземпляра number мы не хотим создания экземпляров класса Dog без конкретного номера; каждый Dog должен иметь таковой. Так что первоочередной задачей при создании экземпляра класса Dog является присваивание значения его переменной экземпляра number. Инициализатор озвучивает это правило и обеспечивает его выполнение — особенно если это назначенный инициализатор класса. Так что давайте считать, что этот инициализатор — назначенный.

 

Кроме того, будем считать, что номер собаки не может изменяться. Когда экземпляр класса Dog начинает свое существование, он получает значение номера, который остается неизменным до конца жизненного цикла экземпляра.

Поэтому удалим метод setNumber: и его объявление, тем самым уничтожив возможность другим классам вмешаться и изменить значение переменной экземпляра number после инициализации. Вместо этого значение переменной экземпляра number устанавливается при инициализации с помощью метода, объявленного как

- (id) initWithNumber: (int) n;

Возвращаемое значение типизировано как id, а не как указатель на объект класса Dog, несмотря на тот факт, что в действительности возвращается объект класса Dog. Это соглашение, которому мы должны подчиняться. Имя метода также соответствует соглашению; как вы знаете, начало метода init говорит о том, что он является инициализатором.

Теперь я хочу показать вам реальный код инициализатора (пример 5.3). Большая часть этого кода обычна — так сказать, обязательная программа инициализатора. Вы не должны спрашивать, зачем — просто сделайте это. Я опишу смысл кода, но не собираюсь оправдывать все части используемого соглашения.

 

Пример 5.3. Схема инициализатора

- (id) initWithNumber: (int) n { self = [super init];0 9 if (self) (
self->_number = n;©
}
return self;®
)

Частями соглашения являются следующие.

  • Мы отправляем сообщение об инициализации, вызывая назначенный инициализатор. Если метод, который мы создаем, является назначенным инициализатором нашего класса, это сообщение отправляется объекту super и вызывает назначенный инициализатор суперкласса. В противном случае оно отправляется объекту self и вызывает назначенный инициализатор данного класса либо другой инициализатор класса, который вызывает назначенный инициализатор. В данном случае мы пишем назначенный инициализатор, а назначенным инициализатором суперкласса является init.
  • Мы сохраняем результат отправки сообщения инициализации в экземпляре self. Для начинающих программистов (и не только для них) оказывается сюрпризом, что можно что-то присвоить self и что это действие имеет смысл. Однако присваивание экземпляра self вполне допустимо (в силу того, как “за кулисами” работает система сообщений языка Objective-C), и это присваивание имеет смысл, поскольку в некоторых случаях экземпляр, возвращаемый отправленным нами сообщением инициализации, может не совпадать с тем экземпляром self, с которым мы начинаем работу.
  • Если значение self не равно nil, мы инициализируем все переменные экземпляра, о которых нам надо позаботиться. Это та часть кода, которую вы будете писать по своему усмотрению; остальное будет соответствовать шаблону. Заметьте, что я не использую никакие методы доступа для установки значений переменных экземпляров (как не использую и свойства); в инициализации переменные экземпляра не наследуются от суперкласса, вы должны выполнять присваивание непосредственно переменной экземпляра.
  • Ранее я упоминал, что переменные экземпляров начинают свое существование с нулевыми значениями, и если это вас не устраивает, то вы можете присвоить им необходимые значения. Ясно, что одним из способов сделать это является написание назначенного инициализатора. Если же нулевые значения по умолчанию вас устраивают, вы можете не беспокоиться о них в назначенном инициализаторе.)
  • Мы возвращаем экземпляр self.

 

Но это не конец. Вспомните, что класс, который определяет назначенный инициализатор, должен также перекрыть унаследованный назначенный инициализатор (в данном случае init). И вы можете увидеть, почему: если этого не сделать, то кто-то сможет сказать [ [Dog alloc] init] (или [Dog new]) и создать собаку без номера — сделать именно то, от чего призван защищать наш инициализатор. Просто в качестве примера я заставлю перекрытый init присваивать отрицательное значение переменной экземпляра number в качестве сигнала о проблемах. Обратите внимание, что мы все еще подчиняемся правилам: этот инициализатор не является назначенным, так что он вызывает назначенный инициализатор данного класса:

- (id) init {

return [self initWithNumber: —9999]; }

Просто чтобы довести дело до конца, вот код, демонстрирующий, как мы теперь можем создать экземпляр класса Dog:

Dog* fido = [[Dog alloc] initWithNumber:42];
int n = fido.number; // n равно 42; наша инициализация работает!

 

Несмотря на то что инициализатор возвращает id, a is является универсальным донором, компилятор предупреждает нас в случае присваивания результата некорректно типизированнной переменной:

NSString* s = [[Dog alloc] initWithNumber:42];
// Предупреждение компилятора

 

Эта магия выполняется современным компилятором LLVM, который из того, что имя этого метода начинается с init, выводит, что это инициализатор, и эффективно заменяет id ключевым словом instancetype, указывающим, что значение, возвращаемое этим методом, должно быть того же типа, что и его получатель (в данном случае — Dog).


 

 

 

Добавить комментарий