Можно ли сделать метод -init закрытым в Objective-C?
Мне нужно скрыть -init
метод моего класса в Objective-C.
Как я могу это сделать?
9 ответов
Objective-C, как и Smalltalk, не имеет понятия "частные" и "публичные" методы. Любое сообщение может быть отправлено любому объекту в любое время.
Что вы можете сделать, это бросить 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 в вашем заголовке. Это должно быть все, что вам нужно в подавляющем большинстве случаев.
NS_UNAVAILABLE
- (instancetype)init NS_UNAVAILABLE;
Это короткая версия недоступного атрибута. Впервые он появился в macOS 10.7 и iOS 5. Это определяется в NSObjCRuntime.h как #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
,
Существует версия, которая отключает метод только для клиентов Swift, но не для кода ObjC:
- (instancetype)init NS_SWIFT_UNAVAILABLE;
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
ованные. При использовании ARC его вызовет компилятор. В любом случае, не о чем беспокоиться, когда вы собираетесь намеренно прекратить исполнение.
objc_designated_initializer
Если вы собираетесь отключить init
для принудительного использования назначенного инициализатора есть атрибут для этого:
-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;
Это генерирует предупреждение, если любой другой метод инициализатора не вызывает myOwnInit
внутренне. Подробности будут опубликованы в " Принятии Modern Objective-C" после следующего выпуска Xcode (я полагаю).
Apple начала использовать следующее в своих заголовочных файлах, чтобы отключить конструктор init:
- (instancetype)init NS_UNAVAILABLE;
Это правильно отображается как ошибка компилятора в XCode. В частности, это установлено в нескольких из их заголовочных файлов HealthKit (HKUnit является одним из них).
Вы можете объявить любой метод недоступным, используя 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
Поместите это в заголовочный файл
- (id)init UNAVAILABLE_ATTRIBUTE;
Если вы говорите о методе -init по умолчанию, то вы не можете. Он унаследован от NSObject, и каждый класс ответит на него без предупреждений.
Вы можете создать новый метод, скажем -initMyClass, и поместить его в закрытую категорию, как предлагает Мэтт. Затем определите метод -init по умолчанию, чтобы вызвать исключение, если оно вызывается, или (лучше) вызвать ваш закрытый -initMyClass с некоторыми значениями по умолчанию.
Одна из основных причин, по которой люди, похоже, хотят скрыть init, - это одноэлементные объекты. Если это так, то вам не нужно скрывать -init, просто верните вместо этого объект-одиночка (или создайте его, если он еще не существует).
Это зависит от того, что вы подразумеваете под "сделать личным". В Objective-C вызов метода для объекта может быть лучше описан как отправка сообщения этому объекту. В языке нет ничего, что запрещало бы клиенту вызывать какой-либо метод на объекте; лучшее, что вы можете сделать, это не объявлять метод в заголовочном файле. Если клиент, тем не менее, вызывает "закрытый" метод с правильной подписью, он все равно будет выполняться во время выполнения.
Тем не менее, наиболее распространенный способ создания закрытого метода в Objective-C - это создание категории в файле реализации и объявление всех "скрытых" методов в нем. Помните, что это не помешает звонкам init
от запуска, но компилятор выдаст предупреждения, если кто-нибудь попытается это сделать.
MyClass.m
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
На MacRumors.com есть достойная ветка на эту тему.
Проблема в том, что вы не можете сделать его "приватным / невидимым", потому что метод init отправляется на id (так как alloc возвращает id), а не на YourClass
Обратите внимание, что с точки зрения компилятора (средства проверки) идентификатор мог бы потенциально реагировать на все, что когда-либо вводилось (он не может проверить, что действительно входит в идентификатор во время выполнения), поэтому вы можете скрыть init только тогда, когда ничего не будет (publicly = in используйте метод init, чтобы компилятор знал, что id не может ответить на init, поскольку нигде нет 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 нигде в нашем проекте не будет публично объявлен (в заголовке), он выдаст предупреждение (id может не отвечать...), и в любом случае человеку, использующему это, потребуется точно знать, что настоящим инициализатором является initOnce
мы могли бы предотвратить это еще дальше, но в этом нет необходимости
Я должен упомянуть, что размещение утверждений и поднятие исключений для методов скрытия в подклассе имеет неприятную ловушку для благонамеренного.
Я бы порекомендовал использовать __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)
И не забывайте о + новом методе класса.