Я хочу заставить пользователя использовать мой собственный метод инициализации (например, -(id)initWithString:(NSString*)foo;
), а не базовый [[myObject alloc]init];
.
Как мне это сделать?
Я хочу заставить пользователя использовать мой собственный метод инициализации (например, -(id)initWithString:(NSString*)foo;
), а не базовый [[myObject alloc]init];
.
Как мне это сделать?
Принятый ответ неверен - вы МОЖЕТЕ сделать это, и это очень просто, вам просто нужно быть немного явным. Вот пример:
У вас есть класс с именем «DontAllowInit», который вы хотите запретить людям:
@implementation DontAllowInit
- (id)init
{
if( [self class] == [DontAllowInit class])
{
NSAssert(false, @"You cannot init this class directly. Instead, use a subclass e.g. AcceptableSubclass");
self = nil; // as per @uranusjr's answer, should assign to self before returning
}
else
self = [super init];
return nil;
}
Объяснение:
Работает отлично. NB: я не рекомендую эту технику для «обычных приложений», потому что обычно вы ВМЕСТО хотите использовать протокол.
ОДНАКО... при написании библиотек... этот метод ОЧЕНЬ ценен: вы часто хотите "спасти (других разработчиков) от самих себя", и это легко сделать NSAssert и сказать им: "Ой! вы пытались выделить/инициализировать неправильный класс ! Вместо этого попробуйте класс X...".
Несмотря на то, что легко просто выйти из строя во время выполнения, когда кто-то вызывает ваш метод, проверка во время компиляции была бы гораздо предпочтительнее.
К счастью, некоторое время это было возможно в Objective-C.
Используя LLVM, вы можете объявить любой метод недоступным в классе, например
- (void)aMethod __attribute__((unavailable("This method is not available")));
Это заставит компилятор жаловаться при попытке вызвать aMethod
. Большой!
Поскольку - (id)init
— это обычный метод, вы можете таким образом запретить вызов стандартного (или любого другого) инициализатора.
Обратите внимание, однако, что это не застрахует от вызова метода с использованием динамических аспектов языка, например, через [object performSelector:@selector(aMethod)]
и т. д. В случае init вы даже не получите предупреждение, потому что метод init определен в других классах, и компилятор не знает достаточно, чтобы выдать вам предупреждение о необъявленном селекторе.
Итак, чтобы предотвратить это, убедитесь, что метод init дает сбой при вызове (см. ответ Адама).
Если вы хотите запретить - (id)init
в фреймворке, не забудьте также запретить + (id)new
, так как это просто перенаправит на инициализацию.
Хави Сото написал небольшой макрос, чтобы быстрее и проще запретить использование назначенного инициализатора и выдавать более приятные сообщения. Вы можете найти его здесь.
tl; dr
Свифт:
private init() {}
Поскольку все классы Swift по умолчанию включают внутреннюю инициализацию, вы можете изменить ее на приватную, чтобы другие классы не вызывали ее.
Цель C:
Поместите это в файл .h вашего класса.
- (instancetype)init NS_UNAVAILABLE;
Это зависит от определения ОС, которое предотвращает вызов указанного метода.
-(id) init
{
@throw [NSException exceptionWithName: @"MyExceptionName"
reason: @"-init is not allowed, use -initWithString: instead"
userInfo: nil];
}
-(id) initWithString: (NSString*) foo
{
self = [super init]; // OK because it calls NSObject's init, not yours
// etc
Генерация исключения оправдана, если вы документируете, что -init
не разрешено и, следовательно, его использование является ошибкой программиста. Однако лучшим ответом было бы заставить -init
вызывать -initWtihString:
с некоторым подходящим значением по умолчанию, т.е.
-(id) init
{
return [self initWithString: @""];
}
-init
суперкласса через [super init]
из нашего назначенного инициализатора. Любые другие инициализаторы суперкласса, которые считаются допустимыми в подклассе, должны быть переопределены, чтобы вместо этого вызывать наш назначенный инициализатор.
- person JeremyP; 18.01.2012
Краткий ответ: вы не можете.
Более подробный ответ: рекомендуется установить наиболее подробный инициализатор в качестве назначенного инициализатора, как описано здесь. Затем 'init' вызовет этот инициализатор с нормальными значениями по умолчанию.
Другой вариант — «утвердить (0)» или сбой другим способом внутри «инициализации», но это не очень хорошее решение.
-init
.
- person JeremyP; 18.01.2012
На самом деле я проголосовал за ответ Адама, но хотел бы добавить к нему кое-что.
Во-первых, настоятельно рекомендуется (как видно из автоматически сгенерированных методов init
в подклассах NSObject
) сверять self
с nil
в init
s. Кроме того, я не думаю, что объекты класса гарантированно будут «равны», как в ==
. я делаю это больше похоже на
- (id)init
{
NSAssert(NO, @"You are doing it wrong.");
self = [super init];
if ([self isKindOfClass:[InitNotAllowedClass class]])
self = nil;
return self;
}
Обратите внимание, что вместо этого я использую isKindOfClass:
, потому что ИМХО, если этот класс запрещает init
, он также должен запрещать его потомкам. Если один из его подклассов хочет его вернуть (что для меня не имеет смысла), он должен явно переопределить его, вызвав мой назначенный инициализатор.
Но что более важно, вне зависимости от того, используете вы описанный выше подход или нет, у вас всегда должна быть соответствующая документация. Вы всегда должны четко указывать, какой метод является вашим назначенным инициализатором, стараться изо всех сил напоминать другим, чтобы они не использовали неподходящие инициализаторы в документации, и доверять другим пользователям/разработчикам, вместо того, чтобы пытаться «спасти чужие задницы» с помощью хитрые коды.