Можно ли сделать метод -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)

И не забывайте о + новом методе класса.

Другие вопросы по тегам