Мне нужно скрыть (сделать приватным) метод -init
моего класса в Objective-C.
Как я могу это сделать?
Мне нужно скрыть (сделать приватным) метод -init
моего класса в Objective-C.
Как я могу это сделать?
Objective-C, как Smalltalk, не имеет понятия "private" против "общедоступных" методов. Любое сообщение может быть отправлено на любой объект в любое время.
Что вы можете сделать, это сбросить NSInternalInconsistencyException
, если вы вызываете метод -init
:
- (id)init {
[self release];
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"-init is not a valid initializer for the class Foo"
userInfo:nil];
return nil;
}
Другая альтернатива, которая, вероятно, намного лучше на практике, состоит в том, чтобы сделать -init
сделать что-то разумное для вашего класса, если это вообще возможно.
Если вы пытаетесь это сделать, потому что вы пытаетесь "обеспечить" один объект-одиночный объект, не беспокойтесь. В частности, не мешайте методу "переопределить +allocWithZone:
, -init
, -retain
, -release
" для создания одиночных чисел. Это практически всегда ненужно и просто добавляет осложнения без каких-либо существенных преимуществ.
Вместо этого просто напишите свой код так, чтобы ваш метод +sharedWhatever
заключался в том, как вы обращаетесь к singleton, и документируйте это как способ получить экземпляр singleton в своем заголовке. Это должно быть все, что вам нужно в подавляющем большинстве случаев.
unavailable
Добавьте атрибут unavailable
в заголовок, чтобы генерировать ошибку компилятора при любом вызове init.
-(instancetype) init __attribute__((unavailable("init not available")));
Если у вас нет причины, просто введите __attribute__((unavailable))
или даже __unavailable
:
-(instancetype) __unavailable init;
doesNotRecognizeSelector:
Используйте doesNotRecognizeSelector:
, чтобы создать исключение NSInvalidArgumentException. "Система времени выполнения вызывает этот метод всякий раз, когда объект получает сообщение aSelector, которое он не может ответить или переадресовать".
- (instancetype) init {
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
NSAssert
Используйте NSAssert
, чтобы выбросить исключение NSInternalInconsistencyException и показать сообщение:
- (instancetype) init {
[self release];
NSAssert(false,@"unavailable, use initWithBlah: instead");
return nil;
}
raise:format:
Используйте raise:format:
, чтобы выбросить собственное исключение:
- (instancetype) init {
[self release];
[NSException raise:NSGenericException
format:@"Disabled. Use +[[%@ alloc] %@] instead",
NSStringFromClass([self class]),
NSStringFromSelector(@selector(initWithStateDictionary:))];
return nil;
}
[self release]
необходим, потому что объект уже был alloc
ated. При использовании ARC компилятор вызовет его для вас. В любом случае, не стоит беспокоиться, когда вы собираетесь намеренно остановить выполнение.
objc_designated_initializer
Если вы намерены отключить init
, чтобы принудительно использовать назначенный инициализатор, для этого есть атрибут:
-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;
Это генерирует предупреждение, если какой-либо другой метод инициализатора не вызовет myOwnInit
внутренне. Подробности будут опубликованы в Принятие современного Objective-C после следующей версии Xcode (я думаю).
Apple начала использовать следующие в своих файлах заголовка, чтобы отключить конструктор init:
- (instancetype)init NS_UNAVAILABLE;
Это правильно отображается как ошибка компилятора в Xcode. В частности, это устанавливается в нескольких файлах заголовка HealthKit (HKUnit является одним из них).
Если вы говорите о методе -init по умолчанию, вы не можете. Он унаследован от NSObject, и каждый класс будет отвечать на него без предупреждений.
Вы можете создать новый метод, скажем, -initMyClass, и поместить его в частную категорию, такую как Мэтт. Затем определите метод -init по умолчанию, чтобы либо вызвать исключение, если оно вызвало, либо (лучше) вызвать ваш private -initMyClass с некоторыми значениями по умолчанию.
Одна из основных причин, по которой люди, похоже, хотят скрыть init, - это singleton objects. Если в этом случае вам не нужно скрывать -init, просто верните объект singleton вместо этого (или создайте его, если он еще не существует).
Поместите это в файл заголовка
- (id)init UNAVAILABLE_ATTRIBUTE;
а проблема, почему вы не можете сделать ее "конфиденциальной/невидимой", является причиной того, что метод init получает send to id (поскольку alloc возвращает идентификатор), а не в YourClass
Обратите внимание, что с точки зрения компилятора (checker) идентификатор может потенциально реагировать на все, что когда-либо было напечатано (он не может проверить, что действительно входит в id во время выполнения), поэтому вы можете скрыть init только тогда, когда ничего не было ( publicly = в заголовке) используйте метод init, который будет знать компилятору, что нет способа для идентификатора для ответа на init, поскольку нет нигде нигде (в вашем источнике, во всех libs и т.д.)
поэтому вы не можете запретить пользователю передавать init и разбиваться на компилятор... но что вы можете сделать, это не дать пользователю получить реальный экземпляр, вызвав init
просто реализуя init, который возвращает nil и имеет (закрытый/невидимый) инициализатор, имя которого кто-то еще не получит (например, initOnce, initWithSpecial...)
static SomeClass * SInstance = nil;
- (id)init
{
// possibly throw smth. here
return nil;
}
- (id)initOnce
{
self = [super init];
if (self) {
return self;
}
return nil;
}
+ (SomeClass *) shared
{
if (nil == SInstance) {
SInstance = [[SomeClass alloc] initOnce];
}
return SInstance;
}
Обратите внимание: что кто-то может это сделать
SomeClass * c = [[SomeClass alloc] initOnce];
и он фактически вернет новый экземпляр, но если initOnce нигде в нашем проекте не будет публично объявлен (в заголовке), он будет генерировать предупреждение (идентификатор может не отвечать...), и в любом случае человек, использующий этот, нужно будет точно знать, что реальным инициализатором является initOnce
мы могли бы предотвратить это еще больше, но нет необходимости
Это зависит от того, что вы подразумеваете под "make private". В Objective-C вызов метода на объект лучше описать как отправку сообщения этому объекту. Там нет ничего на том языке, который запрещает клиенту вызывать какой-либо данный метод для объекта; лучшее, что вы можете сделать, это не объявлять метод в файле заголовка. Если клиент все же вызывает метод "private" с правильной сигнатурой, он все равно будет выполняться во время выполнения.
Тем не менее, наиболее распространенным способом создания частного метода в Objective-C является создание Category в файле реализации и объявите все "скрытые" методы там. Помните, что это действительно не предотвратит запуск вызовов init
, но компилятор выплюнет предупреждения, если кто-то попытается это сделать.
MyClass.m
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
В MacRumors.com есть достойный thread на эту тему.
Я должен упомянуть, что размещение утверждений и сбор исключений для скрытия методов в подклассе имеет неприятную ловушку для хорошо продуманного.
Я бы рекомендовал использовать __unavailable
как Яно объяснил свой первый пример.
Методы могут быть переопределены в подклассах. Это означает, что если метод в суперклассе использует метод, который просто вызывает исключение в подклассе, он, вероятно, не будет работать так, как предполагалось. Другими словами, вы только что сломали то, что использовалось для работы. Это верно и при инициализации. Вот пример такой довольно распространенной реализации:
- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
...bla bla...
return self;
}
- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
return self;
}
Представьте, что произойдет с -initWithLessParameters, если я сделаю это в подклассе:
- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
Это означает, что вы должны использовать частные (скрытые) методы, особенно в методах инициализации, если только вы не планируете переопределять методы. Но это еще одна тема, поскольку вы не всегда имеете полный контроль над реализацией суперкласса. (Это заставляет меня подвергать сомнению использование __attribute ((objc_designated_initializer)) как плохую практику, хотя я не использовал ее в глубину.)
Это также означает, что вы можете использовать утверждения и исключения в методах, которые должны быть переопределены в подклассах. ( "Абстрактные" методы, как в Создание абстрактного класса в Objective-C)
И, не забывайте о методе + новый класс.
Вы можете объявить, что любой метод недоступен с помощью NS_UNAVAILABLE
.
Итак, вы можете поместить эти строки под свой @interface
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
Еще лучше определить макрос в заголовке префикса
#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;
и
@interface YourClass : NSObject
NO_INIT
// Your properties and messages
@end