it-swarm.com.ru

Можно ли сделать метод -init закрытым в Objective-C?

Мне нужно скрыть (сделать личным) метод -init моего класса в Objective-C.

Как я могу это сделать?

139
lajos

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 делать что-то разумное для вашего класса, если это вообще возможно.

Если вы пытаетесь сделать это, потому что вы пытаетесь "гарантировать" использование одноэлементного объекта, не беспокойтесь. В частности, не беспокойтесь о методе "override +allocWithZone:, -init, -retain, -release" для создания синглетонов. Это практически всегда не нужно и просто добавляет усложнение без реального существенного преимущества.

Вместо этого просто напишите свой код таким образом, чтобы ваш метод +sharedWhatever соответствовал тому, как вы осуществляете доступ к одиночному файлу, и документируйте это как способ получения экземпляра одиночного файла в своем заголовке. Это должно быть все, что вам нужно в подавляющем большинстве случаев.

85
Chris Hanson

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")));  

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] необходим, потому что объект уже был allocated. При использовании ARC компилятор вызовет его для вас. В любом случае не о чем беспокоиться, когда вы собираетесь намеренно прекратить исполнение.

objc_designated_initializer

Если вы намереваетесь отключить init для принудительного использования назначенного инициализатора, для этого есть атрибут:

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

Это генерирует предупреждение, если никакой другой метод инициализатора не вызывает myOwnInit внутри. Подробности будут опубликованы в Принятие Modern Objective-C после следующего выпуска Xcode (я думаю).

328
Jano

Apple начала использовать следующее в своих заголовочных файлах, чтобы отключить конструктор init:

- (instancetype)init NS_UNAVAILABLE;

Это правильно отображается как ошибка компилятора в XCode. В частности, это установлено в нескольких из их заголовочных файлов HealthKit (HKUnit - один из них).

100
lehn0058

Если вы говорите о методе -init по умолчанию, то вы не можете. Он унаследован от NSObject, и каждый класс ответит на него без предупреждений.

Вы можете создать новый метод, скажем -initMyClass, и поместить его в закрытую категорию, как предлагает Мэтт. Затем определите метод -init по умолчанию, чтобы вызвать исключение, если оно вызывается, или (лучше) вызвать ваш закрытый -initMyClass с некоторыми значениями по умолчанию.

Одна из главных причин, по которой люди, похоже, хотят скрыть init, - это singleton objects . Если это так, то вам не нужно скрывать -init, просто вместо этого вернуть одиночный объект (или создать его, если он еще не существует).

3
Nathan Kinsinger

Поместите это в заголовочный файл

- (id)init UNAVAILABLE_ATTRIBUTE;
3
Jerry Juang

проблема в том, что вы не можете сделать его "приватным/невидимым", потому что метод init отправляется на id (так как alloc возвращает id), а не на YourClass

Обратите внимание, что с точки зрения компилятора (средства проверки) идентификатор может потенциально реагировать на все, что когда-либо вводилось (он не может проверить, что действительно входит в идентификатор во время выполнения), поэтому вы можете скрыть init только тогда, когда ничего не будет (publicly = in header) используйте метод 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

мы могли бы предотвратить это еще дальше, но в этом нет необходимости

2
Peter Lapisu

Это зависит от того, что вы подразумеваете под "сделать личным". В Objective-C вызов метода для объекта лучше описать как отправку сообщения этому объекту. В языке нет ничего, что запрещало бы клиенту вызывать любой данный метод объекта; лучшее, что вы можете сделать, это не объявлять метод в заголовочном файле. Если клиент, тем не менее, вызывает "закрытый" метод с правильной подписью, он все равно будет выполняться во время выполнения.

Тем не менее, наиболее распространенный способ создания закрытого метода в Objective-C - это создать Category в файле реализации и объявить все "скрытые" методы в нем. Помните, что это не помешает запуску вызовов init, но компилятор выдаст предупреждения, если кто-нибудь попытается это сделать.

MyClass.m

@interface MyClass (PrivateMethods)
- (NSString*) init;
@end

@implementation MyClass

- (NSString*) init
{
    // code...
}

@end

На эту тему есть неплохой тема на MacRumors.com.

1
Matt Dillard

Вы можете объявить любой метод недоступным, используя 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
1
Kaunteya

Я должен упомянуть, что размещение утверждений и поднятие исключений для методов скрытия в подклассе имеет неприятную ловушку для благонамеренного.

Я бы порекомендовал использовать __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 )

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

0
techniao