Какой идиоматический способ управления количеством ссылок во время инициализаторов Obj-C?
Я изучаю Objective-C. В моем первом нетривиальном проекте я столкнулся с вопросом о том, как лучше всего обрабатывать ресурсы, передаваемые инициализатору, по сравнению с инициализатором по умолчанию. У моего класса есть сохраненный ресурс, engine
, который может быть установлен вручную после создания, или во время инициализации явно, или во время инициализации по умолчанию:
- (id)init {
if ((self = [super init])) {
id e = [[XorShiftEngine alloc] init];
[self setEngine: e];
[e release];
}
return self;
}
- (id)initWithEngine:(NSObject <RandEngine> *)e {
if ((self = [super init]))
[self setEngine: e];
return self;
}
- (id)setEngine:(NSObject <RandEngine> *)newEngine {
[newEngine retain];
[engine release];
engine = newEngine;
// Some other stuff which needs to happen on changing the engine.
return engine;
}
Инициализатор по умолчанию, в частности, кажется мне очень уродливым, чередуя код, относящийся к самому себе, затем к члену, затем снова к самому себе, затем снова к члену и называя объект исключительно для возможности его освобождения позже. Это также нарушает обозначенную идиому инициализатора.
Есть ли более идиоматический способ сделать это, не используя пулы автоматического выпуска?
3 ответа
- (id)init {
id e = [[XorShiftEngine alloc] init];
self = [self initWithEngine: e];
[e release];
return self;
}
// designated initializer
- (id)initWithEngine:(NSObject <RandEngine> *)e {
self = [super init];
if (self != nil) {
engine = [e retain];
// engine initialization stuff
}
return self;
}
- (id)setEngine:(NSObject <RandEngine> *)newEngine {
[newEngine retain];
[engine release];
engine = newEngine;
// Some other stuff which needs to happen on changing the engine.
return engine;
}
Вы сделали в основном правильные вещи. Единственное, что я хотел бы изменить, - это непосредственное присвоение переменной и ее сохранение, а не вызов метода установки в методе init.
Apple рекомендует не вызывать установщики / получатели свойств в методах init и dealloc (я предполагаю, что вы объявили свойство retain и просто переопределяете установщик).
Если у вас есть код настройки движка, который должен произойти при первой установке движка в инициализаторе, а также при каждом его изменении, вам следует преобразовать его в отдельный метод.
Вы говорите о e
в -init
? Я не вижу проблемы там... вы создаете объект, вы используете его, вы отпускаете его. Это обычный и правильный шаблон для создания и использования объектов. Если вы недовольны этим, вы могли бы вместо этого иметь -init
передать ноль в -initWithEngine:
и имеют -initWithEngine:
создать двигатель, если ничего не предоставлено.
Люди обычно говорят не об "инициализаторе по умолчанию", а о "назначенном инициализаторе". Назначенный инициализатор - это тот, который вызывают все остальные инициализаторы - часто тот, который учитывает большинство настроек. В этом случае, -initWithEngine:
Ваш назначенный инициализатор.
- init
{
self = [super init];
if (self != nil) {
// initialization here
}
return self;
}
Если у вас есть "инициализаторы покрытия", они должны следовать той же схеме:
- initWithBob:(Bob*)aBob
{
self = [self init];
if ( self != nil ) {
... deal with aBob here ...
}
return self;
}
Это также хороший пример того, почему разумно избегать множественных инициализаторов в классе. Отслеживание может быть затруднительным и делает подклассы более утомительными и подверженными ошибкам. Лучше иметь один инициализатор и несколько подклассов или иметь один инициализатор и позволить экземплярам настраиваться после факта.
Т.е. вместо initWithBob:
, иметь @property(retain) Bob* bob;
это можно назвать после init
по мере необходимости. Ограничение инициализаторов до требуемого состояния является хорошим практическим правилом.
Предполагая, что вы объявляете класс Car, я бы сделал что-то вроде:
@interface Car:NSObject
+ car;
+ carWithEngine:(Engine*)anEngine;
- initWithEngine:(Engine*)anEngine;
@end
Реализации должны быть очевидны; car
создает движок по умолчанию и вызывает alloc/initWithEngine:/autorelease
,
Это дает вам один инициализатор, что делает очевидным простое подклассирование.