IPhone - это initWithCoder исключение из обычного назначенного шаблона проектирования инициализатора?

У меня есть класс MyClass. Он имеет переменные экземпляра, прошедшиеInVar1, passInVar2 и т.д., Значения которых будут переданы от объекта, который запрашивает инициализацию. Он также имеет переменные экземпляра decodedVar1, decodedVar2 и т.д., Которые будут декодированы из архива - или установить значение по умолчанию, если нет архива.

Согласно Apple,

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

Но Apple также говорит, что класс должен иметь один назначенный инициализатор.

Каков наилучший способ справиться со всем этим?

Ответ 1

Яблоки говорят, что:

назначенный инициализатор. метод, который имеет основную ответственность для инициализации новых экземпляров класс. Каждый класс определяет или наследует его собственный назначенный инициализатор. Через сообщения для себя, другие init... методы в одном классе прямо или косвенно ссылаются на назначенный инициализатор и назначенный инициализатор, через сообщение супер, вызывает назначенный инициализатор суперкласс. [emp добавлено]

В принципе, назначенный инициализатор - это один метод init, который вызывает все другие методы init. Однако это не единственный метод init. У каждого класса тоже нет своего. Чаще всего на практике назначенный инициализатор на самом деле является супер-классом init.

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

Например, назначенный инициализатор для UIView initWithFrame:. Итак, UIView initWithCoder выглядит примерно так:

- (id)initWithCoder:(NSCoder *)decoder{
    CGRect theFrame= //...uppack frame data
    self=[self initWithFrame:theFrame];
    return self;
}

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

Это никогда не следует понимать, что класс может иметь только один метод инициализации.

Edit01

Из комментариев:

В частности, как передать значения для некоторых моих иваров, когда инициализация происходит через initWithCoder?

Ну, нет. Вся точка initWithCoder заключается в том, что вы имеете дело со вымороженным сухим экземпляром вашего класса, который содержит все данные, необходимые для воссоздания объекта.

Протокол NSCoding заставляет ваш класс вести себя как креветка, которую они продают как "Морские обезьяны" в комиксах. Методы кодирования обезвоживают/замораживают, высушивая рассольные креветки/экземпляры. Методы декодирования гидратируют рассол-креветки/экземпляры, точно так же, как заливка креветок в воду. Так же, как у рассола-креветок есть все, что им нужно, чтобы начать жить, кроме воды, закодированный объект, сохраненный на диске, имеет все данные, необходимые для воссоздания после инициализации с помощью кодера.

Канонический пример этого - файл nib. Файл nib - это всего лишь куча замороженных экземпляров элементов интерфейса и контроллеров. UIViewController и его UIViews в nib имеют все необходимые им данные, чтобы инициализировать себя, закодированные в xml файла nib. Когда вы вызываете initFromNib напрямую или с помощью IBOutlet, он вызывает каждый метод класса intiWithCoder:.

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

Вы просто установите эти вспомогательные атрибуты после инициализации объекта.

Чтобы встроить назначенный инициализатор, вы просто сначала декодируете, а затем вызываете назначенный инициализатор. Например:

-(id) initWithRequiredValue:(id) someValue otherRequiredValue:(id) anotherValue{
    if (self=[super init]){
        self.requiredProperty=someValue;
        self.anotherRequiredProperty=anotherValue
    }
    return self;
}

Если суперкласс не поддерживает NSCoder, вы начинаете его самостоятельно в подклассе:

- (id)initWithCoder:(NSCoder *)decoder {
    id someDecodedValue=[decoder decodeObjectForKey:@"someValueKey"];
    id someOtherDecodedValue=[decoder decodeObjectForKey:@"someOtherValueKey"];
    self=[self initWithRequiredValue:someDecodedValue otherRequiredValue:someOtherDecodedValue];
    return self;
}

Это простейший случай. Если супер сам поддерживает NSCoding, тогда вы обычно просто заканчиваете тем, что пишете параллельный назначенный инициализатор следующим образом:

- (id)initWithCoder:(NSCoder *)decoder {
    if (self=[super initWithCoder:decoder]){
        id someDecodedValue=[decoder decodeObjectForKey:@"someValueKey"];
        id someOtherDecodedValue=[decoder decodeObjectForKey:@"someOtherValueKey"];
        self.requiredProperty=someDecodedValue;
        self.anotherRequiredProperty=someOtherDecodedValue;
    }
    return self;
}

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

Это один из тех случаев, когда теория и практика не выстраиваются хорошо. Концепция "назначенного инициализатора" применима только к случаям, когда вы создаете экземпляры с нуля.