Можно ли сделать метод -init закрытым в Objective-C? - PullRequest
139 голосов
/ 12 октября 2008

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

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

Ответы [ 9 ]

328 голосов
/ 24 апреля 2011

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

objc_designated_initializer

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

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

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

100 голосов
/ 29 декабря 2014

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

- (instancetype)init NS_UNAVAILABLE;

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

85 голосов
/ 12 октября 2008

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

3 голосов
/ 22 мая 2015

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

- (id)init UNAVAILABLE_ATTRIBUTE;
3 голосов
/ 12 октября 2008

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

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

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

2 голосов
/ 08 декабря 2011

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

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

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

1 голос
/ 11 августа 2017

Вы можете объявить любой метод недоступным, используя 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 голос
/ 12 октября 2008

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

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

MyClass.m

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

@implementation MyClass

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

@end

На MacRumors.com есть достойная ветка на эту тему.

0 голосов
/ 14 мая 2014

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...