iPhone - является ли initWithCoder исключением из обычного назначенного шаблона дизайна инициализатора? - PullRequest
16 голосов
/ 31 мая 2010

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

Согласно Apple ,

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

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

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

1 Ответ

28 голосов
/ 31 мая 2010

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

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

В принципе, указанный инициализатор - это тот метод 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-файл - это просто набор «заморозки» элементов UI и контроллеров. 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;
}

Это самый простой случай. Если super сам поддерживает 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 оказывается параллельным назначенным инициализатором, потому что он заботится обо всей инициализации точно так же, как и назначенный инициализатор. Он не похож на назначенный инициализатор, потому что все его данные предоставляются кодером, но он выполняет ту же функцию.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...