2012-06-13 1 views
2

У меня возникли проблемы с пониманием следующего в книге Стивена Кочана «Программирование в Objective-C» - 4th Edition. Надеюсь, ты поможешь мне это понять.Инициализация объектов в Objective-C

В главе 10 на участке от «инициализации объектов», Стивен пишет:

Вы должны придерживаться следующих два стратегий при написании инициализаторов.

Возможно, вы хотите сделать что-то особенное, когда один из объектов вашего класса будет инициализирован. Например, это идеальное место для создания объектов, которые использует ваш класс, и ссылок через одну или несколько переменных экземпляра. Прекрасным примером этого может быть наш класс Rectangle; было бы разумно выделить начало XYPoint прямоугольника в методе init. Для этого нам просто нужно переопределить унаследованный метод init.

Там стандартная «шаблон», который используется для подмены инициализации, и это выглядит следующим образом:

- (id) init 
{ 
    self = [super init]; 
    if (self) { 
      // initialisation code here. 
    } 

    return self; 
} 

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

Вы должны назначить результат выполнения метода инициализации родительского объекта самостоятельно, поскольку инициализатор имеет право изменить местоположение объекта в памяти (что означает, что его ссылка изменится).

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

ОК, так что до сих пор я понял, что пытается сказать Стивен Кочан, но я полностью озадачен следующей частью. Я надеюсь, что вы можете помочь.

Если класс содержит несколько инициализаторов, один из них должен быть назначенным инициализатором, и все остальные методы его инициализации должны использовать его. Как правило, это ваш самый сложный метод инициализации (обычно тот, который принимает большинство аргументов).

Итак, мой первый вопрос: почему есть все другие методы инициализации, если все они будут использовать один конкретный в этом случае «назначенный» инициализатор?

Стивен Кочан продолжает говорить:

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

Не могли бы вы привести пример этого? Я не совсем уверен, что понял, что он говорит.

Стивен продолжает:

На основе этого обсуждения, ваш метод инициализации initWith: более: для класса фракции может выглядеть следующим образом:

- (Fraction *) initWith:(int)n over:(int)d 
{ 
    self = [super init]; 

    if (self) { 
     [self setTo: n over: d]; 
    } 

    return self; 
} 

После инициализации супер (и его успех, как указано возвратом ненулевого значения), вы используете метод setTo:over: для установки числителя и знаменателя вашей фракции. Как и другие методы инициализации, вы должны вернуть инициализированный объект, который вы здесь делаете.

Программа 10.1 проверяет ваш новый метод инициализации initWith:over:.

#import "Fraction.h" 

int main (int argc, char *argv[]) 
{ 
    @autoreleasepool { 
     Fraction *a, *b; 

     a = [[Fraction alloc] initWith: 1 over: 3]; 
     b = [[Fraction alloc] initWith: 3 over: 7]; 

     [a print]; 
     [b print]; 

    } 

    return 0; 
} 

Выход:
1/3
3/7

До сих пор я понял код. В следующей части я не понимаю:

Чтобы придерживаться правила, изложенного ранее о назначенном инициализаторе, вы также должны изменить init в своем классе фракций. Это особенно важно, если ваш класс может быть подклассом.

Вот что метод инициализации может выглядеть следующим образом:

- (id)init 
{ 
    return [self initWith:0 over:0]; 
} 

Почему это важно, если мы хотим создать подкласс?

Стивен Кочан продолжает:

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

Я тоже не совсем понял эту последнюю часть. Надеюсь, вы сможете помочь.

+0

Ответчик сделает это в ответах ниже; они не собираются отправлять вам по электронной почте. (Я удалил часть вашего вопроса, где вы упомянули об этом.) – CajunLuke

+0

Есть ли проблемы с авторским правом (или проблемы с политикой SO) с цитированием этого большого текста и кода из опубликованной книги? Кажется, это намного больше, чем нужно задать вопрос; делает ли это обычные правила справедливого использования или что-то еще? – abarnert

+1

@abarnert Я так не думаю, но IANAL. Я видел документы на уровне колледжа, которые цитируют больше, поэтому я думаю, что это в основном ОК, пока это явно цитируется, и есть какое-то обсуждение, чтобы пойти с ним. – CajunLuke

ответ

0

«Самый сложный инициализатор» - это тот, который имеет большинство параметров. вы должны сделать все другие init-методы для использования этого:
Может быть initWithValueA:andB:andC: вместе с initWithValueC:, initWithValueA:, initWithValueB:andC: и так далее. все эти «менее сложные» методы должны вызывать «самый сложный» метод, используя значения по умолчанию для всех других параметров (вероятно, 0, nil, ...)
([super init] будет вызывать Init-метод суперкласса, так что вы можете перезаписать init безопасно также называют «наиболее сложный» INIT-метод и настроить свое состояние по умолчанию, или выбросить исключение - если вы хотите, чтобы вызвать пользовательские init вам нужно будет использовать [self init]).

В последней части вы упомянули статический инициализатор («статический конструктор классов»). его объявление начинается с знака «+», что означает, что это метод класса («статический метод»), такой как alloc. в C# это static MyClass() {...}, на Java только static { ... }

+1

На самом деле это не ответ. Вы просто повторяете то, что он уже цитировал. Он спрашивает, почему ему нужны другие методы init, и вы просто скажите ему, как их писать. – abarnert

3

Это целое множество вопросов в одном, что очень сложно ответить.

Итак, мой первый вопрос: почему есть все другие методы инициализации, если все они будут использовать один конкретный в этом случае «назначенный» инициализатор?

Главным образом для удобства ваших абонентов. Например, предположим, что ваш назначенный инициализатор initWithX:y:width:height:, но вы оказываетесь писать такие вещи, как это повсюду:

[[MyRect alloc] initWithX:0 y:0 width:0 height:0] 
[[MyRect alloc] initWithX:myPoint.x y:myPoint.y width:mySize.width height:mySize.height] 

Вы можете добавить еще пару инициализаторах, так что вы можете просто сделать это:

[[MyRect alloc] initWithEmptyRect] 
[[MyRect alloc] initWithPoint:myPoint size:mySize] 

конечно, никогда на самом деле необходимо, но это стоит делать по той же причине, что всегда стоит оборачивать что-то в функции, а не повторять себя снова и снова.

Вы можете найти более реалистичные примеры, просмотрев различные классы, которые поставляются с Foundation/Cocoa - многие из них имеют множество разных инициализаторов, и часто они завершают намного больше работы, чем просто вызывают .x, .y,. ширина и высота. Например, NSDictionary имеет такие методы, как -initWithContentsOfURL:. Теоретически вы всегда можете прочитать содержимое этого URL-адреса, проанализировать plist в пару массивов объектов C и объектов, а затем вызвать -initWithObjects:forKeys:count:, так что это фактически не нужно. Но что бы вы предпочли сделать?

«Создание обозначенную Инициализатора централизует ваш основной код инициализации в одном методе. Любой подклассова вашего класса тогда можно переопределить назначенный Инициализатор, чтобы гарантировать, что новые экземпляры правильно инициализированы».

Не могли бы вы привести пример этого? Я не совсем уверен, что понял , что он говорит.

Скажем, кто-то создает этот класс:

@interface MySuperRect: MyRect 
- (id)initWithX:x y:y width:width height:height; 
@end 

Ему не нужно переопределить все ваши методы инициализации, только этот. Допустим, вы делаете это:

[[MySuperRect alloc] initWithEmptyRect] 

Поскольку MySuperRect не выполнил initWithEmptyRect, это будет использовать реализацию из MyRect, который только называет назначенный инициализатор. Но MySuperRect имеет, реализованный назначенный инициализатор. Таким образом, переопределение будет вызвано.

Я думаю, что это также отвечает на ваш третий вопрос. (Я думаю, что это третий вопрос.)

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

1

Почему все другие методы инициализации, если все они будут использовать один конкретный в этом случае «назначенный» инициализатор?

Вы сделали бы это для удобства. Ваш «назначенный» инициализатор (как упоминает Стивен Кочан) обычно совпадает с большинством аргументов. Все остальные инициализаторы - это удобные методы, которые вызывают это с некоторыми настройками по умолчанию. Например, если у меня есть класс «Автомобиль», где я могу указать число различных элементов:

@implementation Car 

//Designated initializer - has tons of arguments 
- (id)initWithDoors:(int)doors windows:(int)windows wheels:(int)wheels axles:(int)axles 
{ 
    //implementation 
} 

//most axles have two wheels and most doors have a window (and there's the front and back window) 
- (id)initWithDoors:(int)doors wheels:(int) 
{ 
    return [self initWithDoors:doors windows:doors+2 wheels:wheels axles:wheels/2]; 
} 

//most cars have four doors, four wheels, six windows, and two axles 
- (id)init 
{ 
    return [self initWithDoors:4 windows:6 wheels:4 axles:2]; 
} 

@end 

subclasser, то просто вызывает обозначенный инициализатор. Вот пример для класса Coupe. (А купе имеют две двери.)

@implementation Coupe //extends Car 

//our coupe can have a sunroof, which adds a window 
- (id)initWithSunroof:(BOOL)sunroof 
{ 
    self = [super initWithDoors:2 windows:(sunroof?4:5) wheels:4 axles:2]; 
    if (self) { 
     //initialization 
    } 

    return self; 
} 

//a default coupe has no sunroof 
- (id)init 
{ 
    return [self initWithSunroof:NO]; 
} 

@end 

Обратите внимание, что метод -initWithSunroof: является назначенным инициализатором для подкласса.

Почему это важно, если мы хотим подкласс?

Наконец, вы хотите реализовать -init так наивны subclasser можно назвать это и получить все значения по умолчанию, без необходимости исследовать ваш класс слишком много, если они не должны.

Я тоже не совсем понял эту последнюю часть.

Класс инициализатор отформатирован как таковой:

+ (void)initialize 
{ 
    //implementation 
} 

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

+0

+1 для получения более подробных примеров, чем мой ответ. Конечно, вы отвечаете только на один из 4 или более отдельных вопросов, встроенных в первоначальный беспорядок, но это скорее вина допрашивающего, чем ваша. – abarnert

+0

@abarnert Я попытался добавить несколько других вопросов в мое редактирование. – CajunLuke

+0

Жаль, что я не могу повторить ваш ответ +1.Я думаю, что вы все покрыли сейчас, как с лаконичностью, так и с деталями. – abarnert