Можно ли сделать метод -init частным в Objective-C?
Мне нужно скрыть (сделать приватным) метод -init
моего класса в Objective-C.
Как я могу это сделать?
Ответы
Ответ 1
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 в своем заголовке. Это должно быть все, что вам нужно в подавляющем большинстве случаев.
Ответ 2
unavailable
Добавьте атрибут unavailable
в заголовок, чтобы генерировать ошибку компилятора при любом вызове init.
-(instancetype) init __attribute__((unavailable("init not available")));
![compile time error]()
Если у вас нет причины, просто введите __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 (я думаю).
Ответ 3
Apple начала использовать следующие в своих файлах заголовка, чтобы отключить конструктор init:
- (instancetype)init NS_UNAVAILABLE;
Это правильно отображается как ошибка компилятора в Xcode. В частности, это устанавливается в нескольких файлах заголовка HealthKit (HKUnit является одним из них).
Ответ 4
Если вы говорите о методе -init по умолчанию, вы не можете. Он унаследован от NSObject, и каждый класс будет отвечать на него без предупреждений.
Вы можете создать новый метод, скажем, -initMyClass, и поместить его в частную категорию, такую как Мэтт. Затем определите метод -init по умолчанию, чтобы либо вызвать исключение, если оно вызвало, либо (лучше) вызвать ваш private -initMyClass с некоторыми значениями по умолчанию.
Одна из основных причин, по которой люди, похоже, хотят скрыть init, - это singleton objects. Если в этом случае вам не нужно скрывать -init, просто верните объект singleton вместо этого (или создайте его, если он еще не существует).
Ответ 5
Поместите это в файл заголовка
- (id)init UNAVAILABLE_ATTRIBUTE;
Ответ 6
а проблема, почему вы не можете сделать ее "конфиденциальной/невидимой", является причиной того, что метод 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
мы могли бы предотвратить это еще больше, но нет необходимости
Ответ 7
Это зависит от того, что вы подразумеваете под "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 на эту тему.
Ответ 8
Я должен упомянуть, что размещение утверждений и сбор исключений для скрытия методов в подклассе имеет неприятную ловушку для хорошо продуманного.
Я бы рекомендовал использовать __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)
И, не забывайте о методе + новый класс.
Ответ 9
Вы можете объявить, что любой метод недоступен с помощью 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