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