Главная » iPhone, Objective-C, Программирование для iOS и MacOS » init с аргументами Objective-C

0

Иногда нормальная инициализация объекта требует передачи дополнительной информации от метода, который его вызывает. Представьте, что экземпляр Appliance не может нормально функционировать без названия (nil не в счет). В таком случае необходимо как-то передать инициализатору название, закрепляемое за устройством.

Сделать это в init нельзя, потому что init не получает аргументов. Значит,

необходимо создать новый инициализатор. Тогда создание экземпляра Appliance в другом методе будет выглядеть так:

Appliance *a = [[Appliance alloc] initWithProductName:@"Toaster"];

Новый инициализатор класса Appliance – initWithProductName: – получает аргумент NSString. Объявите новый метод в Appliance.h:

#import <Foundation/Foundation.h>

@interface App1iance : NSObject { NSString *рrоduсtNаmе;

int voltage;

}

@property (сору) NSString *productName;

@property int voltage;

-­‐    (id)initWithProductName:(NSString   *)pn;

@end

Найдите     в     Appliance.m     реализацию     init.     Переименуйте     метод     в

initWithProductName: и productName, используя переданное значение.

-­‐    (id)initWithProductName:(NSString   *)рn

{

// Вызов метода init класса NSObject se1f = [super init];

// Метод вернул значение, отличное от nil? if (self) {

// Инициализация названия продукта

[self setProductName:pn];

// Приcваивание начального значения voltage [self setVoltage:120];

}

return self;

}

Прежде чем продолжать, постройте проект и убедитесь в правильности синтаксиса.

Теперь вы можете создать экземпляр Appliance с заданным названием. Но если передать файлы Аррliаnсе.h и Appliance.m другому программисту, он может и не понять, что в коде следует вызвать initwithProduсtNаmе:. Что, если он создаст экземпляр Appliance стандартным способом?

Аррliаnсе *a = [[Аррliаnсе alloc] init];

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

- субкласса NSObject – способен делать все, на что способен экземпляр NSObject. А экземпляры NSObject реагируют на сообщения init.

Однако в данном случае это создает проблемы, потому что приведенная строка кода создает экземпляр Appliance с названием продукта nil и нулевым значением voltage. А ранее мы договорились, что каждый экземпляр Application нормально работает только при значении voltage, равном 120, и названии, отличном от nil. Как предотвратить  подобную  неприятность?

Проблема   решается   просто:   в   файле   Appliance.m   добавьте   метод   init,

вызывающий initWithProductName: с названием по умолчанию.

-­‐  (id)init {

return [self initWithProductName:@"Unknown"];

}

Новая    переопределенная    версия    init    всего    лишь    вызывает    метод

initWithProductName:, который выполняет всю тяжелую работу.

Для  тестирования  двух  инициализаторов  нам  понадобится  метод  description.

Включите его реализацию в Appliance.m:

-­‐   (NSString  *)description

{

return [NSString stringWithFormat:@"<%@: %d volts>", productName, voltage];

}

Немного по экспериментируем с классом в main.m:

#import <Foundation/Foundation.h>

#import "Appliance.h"

int main (int argc, const char * argv[])

{

@autoreleasepool {

Appliance *a = [[Appliance alloc] init]; NSLog(@"a is %@", a);

[a setProductName:@"Washing Machine"]; [a setVoltage:240];

NSLog(@"a is %@", a);

}

return 0;

}

Постройте и запустите программу

Продолжим наше изучение инициализаторов. Создайте новый файл – субкласс

Appliance с именем OwnedAppliance.

Рис. 29.2. Создание субкласса Appliance

Включите в файл OwnedAppliance.h изменяемый набор имен владельцев и три метода.

#import "Appliance.h"

@interface OwnedAppliance : Appliance { NSMutableSet *ownerNames;

}

-­‐   (id)initWithProductName:(NSString  *)pn firstOwnerName:(NSString   *)n;

-­‐    (void)addOwnerNamesObject:(NSString   *)n;

-­‐     (void)removeOwnerNamesObject:(NSString    *)n;

@end

Один из объявленых методов – инициализация, получающий два аргумента. Реализуйте методы в файле OwnedAppliance.m:

#import "OwnedAppliance.h"

@implementation OwnedAppliance

-­‐   (id)initWithProductName:(NSString  *)pn firstOwnerName:(NSString   *)n

{

// вызов инциализатора суперкласса self = [super initWithProductName:pn];

if (self) {

// создание множества для хранения имен владельцев ownerNames = [[NSMutableSet alloc] init];

// имя первго владельца отличного от nil? if (n) {

[ownerNames addObject:n];

}

}

// возвращение указателя на новый объект return self;

}

-­‐    (void)addOwnerNamesObject:(NSString   *)n

{

[ownerNames addObject:n];

}

-­‐     (void)removeOwnerNamesObject:(NSString    *)n

{

}

@end

[ownerNames removeObject:n];

Обратите внимание: класс не инициализирует vo1tage или productName. Этим занимается метод initWithProductName: в классе Appliance. Когда вы создаете субкласс, обычно необходимо инициализировать только те переменные экземпляров, которые в него добавили вы; а о переменных экземпляров, которые добавил суперкласс, пусть позаботится он сам.

Однако теперь мы сталкиваемся с ситуацией, уже знакомой нам по Appliance и инициализатору его суперкласса init. В какой-то момент один из ваших коллег может создать ужасную ошибку, написав следующую строку кода:

OwnedAppliance *a = [[OwnedAppliance alloc] initWithProductName:@"Toaster"];

Этот код приведет к выполнению метода initWithProductName: в классе Appliance. Метод ничего не знает о множестве ownerNames, а следовательно, переменная ownerNames будет некорректно инициализирована для данного экземпляра OwnedAppliance.

Решение     проблемы     тоже     остается     прежним.     Включите     в     файл

OwnedAppliance.m реализацию инициализатора суперкласса initWithProductName:, которая вызывает initWithProductName:firstOwnerName: и передает значение по умолчанию для firstOwnerName.

-­‐    (id)initWithProductName:(NSString   *)pn

{

return [self initWithProductName:pn firstOwnerName:nil];

}

Вопрос на засыпку: нужно ли также реализовать init в OwnedAppliance? Нет. На этой стадии следующий код будет нормально работать:

OwnedAppliance *a = [[OwnedAppliance alloc] init];

Почему? Потому что при отсутствии реализации init в OwnedAppliance эта строка приведет к срабатыванию реализации init из Appliance, которая вызывает [self initWithProductName:@"Unknown"].self – экземпляр OwnedAppliance, поэтому он вызывает версию initWithProductName: класса OwnedAppliance, которая вызывает

[self initWithProductName: pn firstOwnerName:nil].

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

Рис 29.3. Цепочка инициализаторов

На рис. 29.3 один инициализатор для каждого класса выделен темным фоном. Он является основным инициализатором для данного класса. Так, init является основным инициализатором для класса NSObject, initWithProductName: – для класса Appliance, а initWithProductName:firstOwnerName: – для класса ОwnedAppliance. Класс содержит только один основной инициализатор. Если класс содержит другие инициализаторы, то их реализация должна вызывать (прямо или косвенно) основной инициализатор.

При создании класса, у которого имя основного инициализатора отличается от

имени основного инициализатора суперкласса (как у Appliance с OwnedAppliance), необходимо документировать это обстоятельство в заголовочном файле. Включите соответствующий комментарий в Appliance.h:

#import <Foundation/Foundation.h>

@interface Appliance : NSObject { NSString *productName;

int voltage;

}

@property (copy) NSString *productName;

@property int voltage;

// основной инциализатор

-­‐  (id)initWithProductName:(NSString  *)pn;  @end and  in  OwnedAppliance.h:

#import "Appliance.h"

@interface OwnedAppliance : Appliance { NSMutableSet *ownerNames;

}

// основной инциализатор

-­‐   (id)initWithProductName:(NSString  *)pn firstOwnerName:(NSString   *)n;

-­‐    (void)addOwnerNamesObject:(NSString   *)n;

-­‐     (void)removeOwnerNamesObject:(NSString    *)n;

@end

Итак,  мы  подходим  к  правилам,  которые  должны  соблюдаться  каждым приличным программистом Objective-C при написании инициализаторов:

•  Если класс имеет несколько инициализаторов, только один из них должен выполнять реальную работу. Этот метод называется основным инцициалuзатором. Все остальные инициализаторы Должны вызывать основной инициализатор (прямо или косвенно).

•  Основной инициализатор вызывает основной инициализатор суперкласса перед инициализацией своих переменных экземпляров.

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

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

Источник: Аарон Хилегас, «Objective-C. Программирование для iOS и MacOS», 2012 г.

По теме:

  • Комментарии