Альтернативой получению готового экземпляра является явное требование к классу создать экземпляр.

Для этого классу отправляется сообщение alloc. Метод класса alloc реализован классом NSObject, корневым классом, из которого наследуются все иные классы. Он приводит к тому, что для экземпляра выделяется необходимая память, так что указатель на экземпляр может на нее указывать. (Управление этой памятью — отдельный вопрос, который рассматривается в главе 12.)

Вы никогда, никогда, никогда не должны вызывать метод alloc сам по себе. Вы должны немедленно вызвать другой метод, метод экземпляра, который инициализирует вновь созданный экземпляр, вводит его в известное корректное состояние, так что ему могут отправляться другие сообщения. Такой метод именуется инициализатором. Кроме того, инициализатор возвращает экземпляр — обычно тот же самый, но уже инициализированный. Следовательно, вы можете (и всегда должны) вызывать alloc и инициализатор в одной строке кода. Минимальным инициализатором является init. Такой базовый шаблон, неформально известный как “alloc—init”, показан в примере 5.1.

 

Пример 5.1. Базовый шаблон создания с нуля

SomeClass* aVariable = [[SomeClass alloc] init];

 

Инициализация

Вы не можете создать экземпляр с нуля, если не знаете, как выполнить инициализацию, так что мы тотчас же перейдем к этому важному вопросу.

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

Основной шаблон инициализации, как показано в примере 5.1, представляет собой вызов alloc, вложенный в вызов инициализатора, результат вызова которого (не alloc!) присваивается переменной. Одной из причин для такой вложенной структуры является то, что если что-то идет не так и экземпляр не может быть создан или инициализирован, то инициализатор вернет значение nil; следовательно, очень важно сохранить результат инициализации и проверить его значение (не результат alloc!) на равенство nil.

Для того чтобы помочь определить инициализаторы, все они именуются в соответствии с определенным соглашением, согласно которому все инициализаторы, и только инициализаторы имеют имена, начинающиеся с init. Простейший инициализатор называется init и не принимает ни одного параметра. Прочие инициализаторы принимают параметры, и их имена обычно начинаются с фразы initWith, за которой следует описание их параметров. Например, документация класса NSArray перчисляет следующие методы инициализации:

- initWithArray:
- initWithArray:copyItems:
- initWithContentsOfFile:
- initWithContentsOfURL:
- initWithObjects:
- initWithObjects:count:

Рассмотрим реальный пример. В главе 3, мы создавали массив NSArray из трех строк, составляющих его элементы, с помощью фабричного метода arrayWithObjectsпринимающего список объектов, завершающийся nil, и возвращающий готовый экземпляр:

NSArray* pep =
[NSArray arrayWithObjects"Manny", @”Moe", 8"Jack", nil];

Оказывается, однако, что имеется также инициализатор NSArray с именем initWithOb j ects:, который работает точно таким же образом, как и arrayWithOb j ects:. Отличие заключается в том, что последний является фабричным методом класса, который возвращает готовый объект, в то время как первый является инициализатором, методом экземпляра, который может использоваться только в одном ряду с alloc. Таким образом, мы можем выполнить в точности те же действия, что и в главе 3, с тем отличием, что в этот раз мы сами создаем экземпляры с нуля:

NSArray* pep =
[NSArray alloc] initWithObjects:0"Manny", @"Moe",
0"Jack", nil];

В современной версии языка Objective-C, как я упоминал в главе 3, вы вряд ли будете вызывать arrayWithObjects: или initWithObjectsпоскольку теперь имеется удобный литеральный синтаксис массивов, который генерирует массив на основе списка его содержимого:

NSArray* pep = @[@"Manny", 0"Moe", @"Jack"];

Поэтому я приведу другой пример. Предположим, что, так или иначе, но у вас есть массив рер, содержащий три строки, @"Маппу", 0"Мое" и Jack", и вы хотите создать на основе первого массива второй, который содержит те же три строки. Заметим, что для этого недостаточно просто присвоить другой переменной NSArray значение переменной рер:

NSArray* рер2 = рер; // Нет, этим другой массив не создать

Ссылки на объекты являются указателями, и присваивание указателей просто приводит к тому, что две ссылки указывают на одну и ту же сущность (глава 3). Так что рер2 в этом коде не является вторым массивом; это тот же самый массив, что, определенно, не то, что нам хотелось получить. Чтобы получить второй экземпляр массива на основе первого, мы можем вызвать метод класса arrayWithArray: следующим образом:

NSArray* рер2 = [NSArray arrayWithArray: pep];

Теперь рер2 представляет собой новоиспеченный экземпляр, отличный от рер. Это готовый экземпляр, возвращаемый фабричным методом класса. И вновь оказывается, что

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

NSArray* рер2 = [[NSArray alloc] initWithArray: pep];

Часто бывает так, что встроенный класс каркаса Cocoa предлагает фабричный метод и инициализатор, которые, начиная с одного и того же типа данных, производят один и тот же результат. В конечном счете нет никакой разницы, что именно вы используете; при одних и тех же аргументах оба подхода дают экземпляры, неотличимые друг от друга. (Эти два подхода имеют различные последствия для управления памятью, как я поясню в главе 12, но при использовании ARC эти различия, вероятно, не будут играть для вас никакой роли.)

Просматривая документацию об инициализаторах, не забывайте пройтись вверх по иерархии классов. Например, в документации класса UlWebView инициализаторы не перечислены, но класс UlWebView наследуется от UlView, в документации класса UlView вы можете обнаружить метод initWithFrame:. Кроме того, метод init определен как метод экземпляра класса NSObject, так что каждый класс наследует его, и сообщение init может быть отправлено всем вновь созданным экземплярам. Таким образом, если класс не определяет своего собственного инициализатора, можно инициализировать его экземпляр с помощью метода init. Например, в документации класса UIResponder нет ни инициализаторов, ни фабричных методов. Значит, чтобы создать экземпляр UIResponder с нуля, вы должны вызвать методы alloc и init.

 

Если вы планируете вызывать инициализатор init, последовательные вызовы методов alloc и init можно объединить в вызов метода класса new. Другими словами, [MyClass new] представляет собой синоним для [ [MyClass alloc] init]. Это удобное сокращение, но оно применимо только тогда, когда инициализатором является метод init; для использования любого другого инициализатора необходимо вызывать метод alloc и требуемый инициализатор явно.

 

Назначенный инициализатор

Если класс определяет инициализаторы, один из них может быть описан в документации как назначенный инициализатор. (В имени метода нет ничего, что говорило бы о том, что это назначенный инициализатор; для того чтобы это определить, следует обратиться к документации.) Например, в документации класса UlView метод initWithFrame: описан как назначенный инициализатор. Класс, который не определяет назначенный инициализатор, наследует назначенный инициализатор своего суперкласса. Последним назначенным инициализатором, наследуемым всеми классами без иных назначенных инициализаторов, является init. Таким образом, каждый класс имеет ровно один назначенный инициализатор.

 

Назначенный инициализатор класса должен вызываться в процессе создания экземпляра класса. Если у класса имеется несколько инициализаторов, неважно, унаследованных или определенных в классе, назначенным инициализатором является инициализатор, от которого зависят другие инициализаторы: в конечном итоге они должны его вызывать.

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

  • Документация класса NSDate гласит, что назначенным инициализатором является in itWithTimelntervalSinceReferenceDate: и что другие инициализаторы (такие, как initWithTimelntervalSinceNow:) вызывают его.
  • Документация класса UlView гласит, что назначенным инициализатором является initWithFrame:. Класс UlView не имеет других инициализаторов, но их имеют некоторые из его подклассов. Класс UlWebView, подкласс UlView, не имеет инициализатора, так что initWithFrame: является его назначенным инициализатором (посредством наследования). Класс UllmageView, подкласс UlView, имеет инициализаторы, такие как initWithlmage:, но ни один из них не является назначенным; таким образом, initWithFrame: является назначенным инициализатором для этого класса, и initWithlmage: должен вызывать initWithFrame:.

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

  • Класс NSDate выполняет замещение унаследованного метода init для вызова собственного назначенного инициализатора, initWithTimelntervalSinceReferenceDat е:, со значением, которое генерирует дату, определяемую текущим моментом времени.
  • Класс UlView замещает унаследованный метод init для вызова собственного назначенного инициализатора, initWithFrame:, со значением рамки CGRectZero.

 

 

 

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