Как запретить базовый метод init в NSObject
Я хочу заставить пользователя использовать мой собственный метод init (например, -(id)initWithString:(NSString*)foo;
), а не базовый [[myObject alloc]init];
.
как я могу это сделать?
Ответы
Ответ 1
Принятый ответ неверен - вы МОЖЕТЕ это сделать, и это очень просто, вы просто должны быть немного явным. Вот пример:
У вас есть класс с именем "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 answer, should assign to self before returning
}
else
self = [super init];
return nil;
}
Пояснение:
- Когда вы вызываете [super init], класс, который был alloc'd, был SUBCLASS.
- "self" - это экземпляр, т.е. вещь, которая была init'd
- "[self class]" - это экземпляр класса, который будет SUBCLASS, когда SUBCLASS вызывает [super init] или будет SUPERCLASS, когда SUPERCLASS вызывается с простым [[SuperClass alloc] init]
- Итак, когда суперкласс получает вызов "init", ему просто нужно проверить, является ли класс alloc'd тем же, что и его собственный класс
Работает отлично. NB: Я не рекомендую эту технику для "обычных приложений", потому что обычно вы хотите использовать протокол INSTEAD.
ОДНАКО... при написании библиотек... этот метод ОЧЕНЬ ценен: вы часто хотите "сохранить (других разработчиков) от себя", а его легко NSAssert и сказать им "Ой, вы пытались выделить /init неправильный класс! Попробуйте вместо класса X...".
Ответ 2
Все остальные ответы здесь устарели. Теперь можно сделать это правильно!
В то время как при запуске, когда кто-то вызывает ваш метод, легко просто сбой, проверка времени компиляции будет намного предпочтительнее.
К счастью, это было возможно в 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
, поскольку это будет просто перенаправлено на init.
Джави Сото написал небольшой макрос, чтобы запретить использование назначенного инициализатора быстрее и проще и давать более приятные сообщения. Вы можете найти здесь здесь.
Ответ 3
TL; д-р
Swift
private init() {}
Поскольку все классы Swift включают по умолчанию внутренний init, вы можете изменить его на private, чтобы другие классы не вызывали его.
Цель C:
Поместите это в ваш .h файл.
- (instancetype)init NS_UNAVAILABLE;
Это зависит от определения ОС, которое предотвращает вызов имени метода.
Ответ 4
-(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 init, not yours
// etc
Выбрасывание исключения оправдано, если вы документируете, что -init
не разрешено, и поэтому использование его является ошибкой программиста. Однако лучшим ответом будет сделать -init
invoke -initWtihString:
с некоторым подходящим значением по умолчанию i.e.
-(id) init
{
return [self initWithString: @""];
}
Ответ 5
Я фактически проголосовал за ответ Адама, но хотел бы добавить к нему некоторые вещи.
Во-первых, настоятельно рекомендуется (как показано в автогенерированных методах 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:
вместо этого, потому что IMHO, если этот класс запрещает init
, он должен также запретить своим потомкам иметь его. Если один из его подклассов хочет вернуть его (что не имеет смысла для меня), он должен явно переопределить его, вызвав мой назначенный инициализатор.
Но что еще более важно, придерживайтесь вышеуказанного подхода или нет, вы должны всегда иметь соответствующую документацию. Вы всегда должны четко указывать, какой метод является вашим назначенным инициализатором, старайтесь как можно лучше, чтобы напомнить другим, чтобы не использовать несоответствующие инициализаторы в документации и не доверять другим пользователям/разработчикам, вместо того, чтобы пытаться "спасти всех остальных ослов" с помощью умные коды.
Ответ 6
Короткий ответ: вы не можете.
Более длинный ответ: наилучшей практикой является установка самого подробного инициализатора в качестве назначенного инициализатора, как описано здесь. "init" затем вызовет этот инициализатор с нормальными значениями по умолчанию.
Другой вариант - "assert (0)" или сбой другим способом внутри "init", но это не очень хорошее решение.