É possível tornar o método -init privado em Objective-C?

Eu preciso esconder (tornar privado) o método -init da minha class em Objective-C.

Como eu posso fazer isso?

O Objective-C, como o Smalltalk, não tem conceito de methods “privados” versus “públicos”. Qualquer mensagem pode ser enviada para qualquer object a qualquer momento.

O que você pode fazer é lançar uma NSInternalInconsistencyException se o método -init for invocado:

 - (id)init { [self release]; @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"-init is not a valid initializer for the class Foo" userInfo:nil]; return nil; } 

A outra alternativa – que provavelmente é muito melhor na prática – é fazer -init fazer algo sensato para sua class, se possível.

Se você está tentando fazer isso porque está tentando “garantir” que um object singleton seja usado, não se incomode. Especificamente, não se incomode com o +allocWithZone: “override +allocWithZone: -init , -retain , -release ” de criar singletons. É praticamente sempre desnecessário e está apenas adicionando complicação sem nenhuma vantagem significativa real.

Em vez disso, apenas escreva seu código de modo que seu método +sharedWhatever é como você acessa um singleton, e documente isso como a maneira de obter a instância singleton em seu header. Isso deve ser tudo o que você precisa na grande maioria dos casos.

NS_UNAVAILABLE

 - (instancetype)init NS_UNAVAILABLE; 

Esta é uma versão curta do atributo indisponível. Apareceu pela primeira vez no macOS 10.7 e no iOS 5 . Ele é definido em NSObjCRuntime.h como #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE .

Existe uma versão que desativa o método apenas para clientes Swift , não para o código ObjC:

 - (instancetype)init NS_SWIFT_UNAVAILABLE; 

unavailable

Adicione o atributo unavailable ao header para gerar um erro do compilador em qualquer chamada para o init.

 -(instancetype) init __attribute__((unavailable("init not available"))); 

erro de tempo de compilação

Se você não tem um motivo, apenas digite __attribute__((unavailable)) , ou até __unavailable :

 -(instancetype) __unavailable init; 

doesNotRecognizeSelector:

Use doesNotRecognizeSelector: para criar um NSInvalidArgumentException. “O sistema de tempo de execução invoca esse método sempre que um object recebe uma mensagem aSelector que não pode responder ou encaminhar.”

 - (instancetype) init { [self release]; [super doesNotRecognizeSelector:_cmd]; return nil; } 

NSAssert

Use o NSAssert para lançar NSInternalInconsistencyException e mostrar uma mensagem:

 - (instancetype) init { [self release]; NSAssert(false,@"unavailable, use initWithBlah: instead"); return nil; } 

raise:format:

Use raise:format: para lançar sua própria exceção:

 - (instancetype) init { [self release]; [NSException raise:NSGenericException format:@"Disabled. Use +[[%@ alloc] %@] instead", NSStringFromClass([self class]), NSStringFromSelector(@selector(initWithStateDictionary:))]; return nil; } 

[self release] é necessário porque o object já foi alloc . Ao usar o ARC, o compilador irá chamá-lo para você. Em qualquer caso, não é algo para se preocupar quando você está prestes a interromper intencionalmente a execução.

objc_designated_initializer

Caso você pretenda desativar o init para forçar o uso de um inicializador designado, há um atributo para isso:

 -(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER; 

Isso gera um aviso, a menos que qualquer outro método inicializador chame myOwnInit internamente. Os detalhes serão publicados em Adopting Modern Objective-C após a próxima versão do Xcode (eu acho).

A Apple começou a usar o seguinte em seus arquivos de header para desativar o construtor de boot:

 - (instancetype)init NS_UNAVAILABLE; 

Isso exibe corretamente como um erro do compilador no Xcode. Especificamente, isso é definido em vários dos seus arquivos de header HealthKit (HKUnit é um deles).

Se você está falando sobre o método default -init, então você não pode. Ele é herdado do NSObject e todas as classs responderão sem aviso.

Você poderia criar um novo método, digamos -initMyClass, e colocá-lo em uma categoria privada como Matt sugere. Em seguida, defina o método default -init para gerar uma exceção se ela for chamada ou (melhor) chamar seu private -initMyClass com alguns valores padrão.

Uma das principais razões pelas quais as pessoas parecem querer ocultar o init é para objects singleton . Se esse for o caso, você não precisará ocultar -init, apenas retorne o object singleton (ou crie-o se ele ainda não existir).

Coloque isso no arquivo de header

 - (id)init UNAVAILABLE_ATTRIBUTE; 

Bem, o problema porque você não pode torná-lo “privado / invisível” é porque o método init é enviado para id (como alocação retorna um id) não para YourClass

Note que do ponto do compilador (checker) um id poderia responder potencialmente a qualquer coisa já digitada (ele não pode checar o que realmente entra no id em tempo de execução), então você poderia esconder o init somente quando nada iria (publicamente = em header) use um método init, do que a compilation saberia, que não há como o id responder ao init, já que não existe nenhum init em qualquer lugar (na sua fonte, todas as libs etc …)

então você não pode proibir o usuário de passar o init e ser esmagado pelo compilador … mas o que você pode fazer é evitar que o usuário obtenha uma instância real chamando um init

simplesmente implementando o init, que retorna nil e possui um inicializador (privado / invisível) cujo nome alguém não obterá (como 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; } 

Nota: que alguém poderia fazer isso

 SomeClass * c = [[SomeClass alloc] initOnce]; 

e, de fato, retornaria uma nova instância, mas se a initOnce não fosse declarada em nosso projeto publicamente (no header), geraria um aviso (o id poderia não responder …) e, de qualquer forma, a pessoa que usa isso precisaria saber exatamente que o inicializador real é o initOnce

poderíamos evitar isso ainda mais, mas não há necessidade

Isso depende do que você entende por “tornar privado”. Em Objective-C, chamar um método em um object pode ser melhor descrito como o envio de uma mensagem para esse object. Não há nada na linguagem que proíba um cliente de chamar qualquer método em um object; O melhor que você pode fazer é não declarar o método no arquivo de header. Se um cliente, no entanto, chamar o método “privado” com a assinatura correta, ele ainda será executado no tempo de execução.

Dito isso, a maneira mais comum de criar um método particular em Objective-C é criar uma Categoria no arquivo de implementação e declarar todos os methods “ocultos” existentes. Lembre-se de que isso não impedirá que as chamadas para o init sejam executadas, mas o compilador emitirá avisos se alguém tentar fazer isso.

MyClass.m

 @interface MyClass (PrivateMethods) - (NSString*) init; @end @implementation MyClass - (NSString*) init { // code... } @end 

Há um segmento decente no MacRumors.com sobre este tópico.

Você pode declarar qualquer método para não estar disponível usando NS_UNAVAILABLE .

Então você pode colocar essas linhas abaixo da sua @interface

 - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; 

Ainda melhor definir uma macro no seu header de prefixo

 #define NO_INIT \ - (instancetype)init NS_UNAVAILABLE; \ + (instancetype)new NS_UNAVAILABLE; 

e

 @interface YourClass : NSObject NO_INIT // Your properties and messages @end 

Eu tenho que mencionar que colocar afirmações e criar exceções para ocultar methods na subclass tem uma armadilha desagradável para os bem-intencionados.

Eu recomendaria usar __unavailable como Jano explicou em seu primeiro exemplo .

Métodos podem ser substituídos em subclasss. Isto significa que se um método na superclass usa um método que apenas levanta uma exceção na subclass, provavelmente não funcionará como pretendido. Em outras palavras, você acabou de quebrar o que costumava funcionar. Isso também é verdade com os methods de boot. Aqui está um exemplo dessa implementação bastante comum:

 - (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2 { ...bla bla... return self; } - (SuperClass *)initWithLessParameters:(Type1 *)arg1 { self = [self initWithParameters:arg1 optional:DEFAULT_ARG2]; return self; } 

Imagine o que acontece com -initWithLessParameters, se eu fizer isso na subclass:

 - (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2 { [self release]; [super doesNotRecognizeSelector:_cmd]; return nil; } 

Isto implica que você deve tender a usar methods privados (ocultos), especialmente em methods de boot, a menos que você planeje ter os methods sobrescritos. Mas, esse é outro tópico, já que nem sempre você tem controle total na implementação da superclass. (Isso me faz questionar o uso de __attribute ((objc_designated_initializer)) como prática ruim, embora eu não tenha usado isso em profundidade.)

Isso também implica que você pode usar asserções e exceções em methods que devem ser substituídos em subclasss. (Os methods “abstratos” como em Criando uma class abstrata em Objective-C )

E não se esqueça do + novo método de class.