Preciso ocultar (tornar privado) o -init
método da minha classe no Objective-C.
Como eu posso fazer isso?
objective-c
lajos
fonte
fonte
NS_UNAVAILABLE
. Geralmente, exorto você a usar essa abordagem. O OP consideraria revisar sua resposta aceita? As outras respostas aqui fornecem muitos detalhes úteis, mas não são o método preferido para conseguir isso.NS_UNAVAILABLE
ainda permite que um chamador invoqueinit
indiretamente vianew
. Simplesmente substituirinit
para retornarnil
tratará dos dois casos.Respostas:
O Objetivo-C, como Smalltalk, não tem conceito de métodos "privados" versus "públicos". Qualquer mensagem pode ser enviada para qualquer objeto a qualquer momento.
O que você pode fazer é lançar um
NSInternalInconsistencyException
se seu-init
método for chamado:A outra alternativa - que provavelmente é muito melhor na prática - é
-init
fazer algo sensato para a sua classe, se possível.Se você está tentando fazer isso porque está tentando "garantir" que um objeto singleton seja usado, não se preocupe. Especificamente, não se incomode com o "override
+allocWithZone:
,-init
,-retain
,-release
" método de criação de singletons. É quase sempre desnecessário e está apenas adicionando complicações sem nenhuma vantagem significativa.Em vez disso, basta escrever seu código de forma que seu
+sharedWhatever
método seja como você acessa um singleton e documentar isso como forma de obter a instância singleton em seu cabeçalho. Isso deve ser tudo o que você precisa na grande maioria dos casos.fonte
alloc
einit
e têm a sua função de código de forma incorrecta, porque eles têm a classe certa, mas a instância errada. Essa é a essência do princípio do encapsulamento no OO. Você oculta coisas em suas APIs às quais outras classes não precisam ou têm acesso. Você não apenas mantém tudo público e espera que os humanos acompanhem tudo.NS_UNAVAILABLE
Esta é a versão curta do atributo indisponível. Ele apareceu pela primeira vez no macOS 10.7 e iOS 5 . É 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:
unavailable
Adicione o
unavailable
atributo ao cabeçalho para gerar um erro do compilador em qualquer chamada para init.Se você não tiver um motivo, basta digitar
__attribute__((unavailable))
ou até__unavailable
:doesNotRecognizeSelector:
Use
doesNotRecognizeSelector:
para gerar uma NSInvalidArgumentException. "O sistema de tempo de execução chama esse método sempre que um objeto recebe uma mensagem aSelector que não pode responder ou encaminhar."NSAssert
Use
NSAssert
para lançar NSInternalInconsistencyException e mostrar uma mensagem:raise:format:
Use
raise:format:
para lançar sua própria exceção:[self release]
é necessário porque o objeto já foialloc
atado. Ao usar o ARC, o compilador o chamará para você. De qualquer forma, não é algo para se preocupar quando você está prestes a parar intencionalmente a execução.objc_designated_initializer
Caso você pretenda desativar
init
para forçar o uso de um inicializador designado, há um atributo para isso:Isso gera um aviso, a menos que qualquer outro método inicializador chame
myOwnInit
internamente. Os detalhes serão publicados em Adotting Modern Objective-C após o próximo lançamento do Xcode (eu acho).fonte
init
. Como esse método é inválido, por que você inicializa um objeto? Além disso, ao lançar uma exceção, você poderá especificar alguma mensagem personalizada comunicando oinit*
método correto ao desenvolvedor, enquanto não tiver essa opção no caso dedoesNotRecognizeSelector
.A Apple começou a usar o seguinte em seus arquivos de cabeçalho para desativar o construtor init:
Isso é exibido corretamente como um erro de compilador no Xcode. Especificamente, isso é definido em vários de seus arquivos de cabeçalho HealthKit (o HKUnit é um deles).
fonte
Se você está falando sobre o método -init padrão, não pode. Ele é herdado do NSObject e todas as classes responderão a ele sem avisos.
Você pode criar um novo método, digamos -initMyClass, e colocá-lo em uma categoria particular, como sugere Matt. Em seguida, defina o método -init padrão para gerar uma exceção se for chamado ou (melhor) chamar seu -initMyClass privado com alguns valores padrão.
Uma das principais razões pelas quais as pessoas parecem querer ocultar o init é para objetos singleton . Se for esse o caso, não será necessário ocultar -init, basta retornar o objeto singleton (ou crie-o se ainda não existir).
fonte
Coloque isso no arquivo de cabeçalho
fonte
Você pode declarar que qualquer método não está disponível usando
NS_UNAVAILABLE
.Então você pode colocar essas linhas abaixo do seu @interface
Ainda melhor, defina uma macro no cabeçalho do prefixo
e
fonte
Isso depende do que você quer dizer com "tornar privado". No Objective-C, chamar um método em um objeto pode ser melhor descrito como enviar uma mensagem para esse objeto. Não há nada na linguagem que proíba um cliente de chamar qualquer método em um objeto; o melhor que você pode fazer é não declarar o método no arquivo de cabeçalho. No entanto, se um cliente chamar o método "private" com a assinatura correta, ele ainda será executado em tempo de execução.
Dito isso, a maneira mais comum de criar um método privado no Objective-C é criar uma categoria no arquivo de implementação e declarar todos os métodos "ocultos" nele. Lembre-se de que isso realmente não impedirá a
init
execução de chamadas , mas o compilador emitirá avisos se alguém tentar fazer isso.MyClass.m
Há uma discussão decente no MacRumors.com sobre esse tópico.
fonte
Bem, o problema por que você não pode torná-lo "privado / invisível" é porque o método init é enviado para o ID (pois o atributo retorna um ID), não para o YourClass
Observe que, a partir do ponto do compilador (verificador), um ID pode responder potencialmente a qualquer coisa digitada (não pode verificar o que realmente entra no ID em tempo de execução); portanto, você pode ocultar o init apenas quando nada em lugar algum (publicamente = in cabeçalho) use um método init, do que a compilação saberia, que não há como o id responder ao init, pois não existe init em nenhum lugar (em sua fonte, todas as bibliotecas etc ...)
então você não pode proibir que o usuário passe init e seja esmagado pelo compilador ... mas o que você pode fazer é impedir que o usuário obtenha uma instância real chamando um init
simplesmente implementando init, que retorna nulo e possui um inicializador (privado / invisível) cujo nome alguém não receberá (como initOnce, initWithSpecial ...)
Nota: alguém poderia fazer isso
e, de fato, retornaria uma nova instância, mas se o initOnce em nenhum lugar do nosso projeto fosse declarado publicamente (no cabeçalho), isso geraria um aviso (o id pode não responder ...) e, de qualquer maneira, a pessoa que o usa, precisará saber exatamente que o inicializador real é o initOnce
poderíamos evitar isso ainda mais, mas não há necessidade
fonte
Devo mencionar que colocar afirmações e criar exceções para ocultar métodos na subclasse tem uma armadilha desagradável para os bem-intencionados.
Eu recomendaria usar
__unavailable
como Jano explicou em seu primeiro exemplo .Os métodos podem ser substituídos nas subclasses. Isso significa que, se um método na superclasse usar um método que apenas gera uma exceção na subclasse, provavelmente não funcionará como pretendido. Em outras palavras, você acabou de quebrar o que costumava funcionar. Isso também ocorre com os métodos de inicialização. Aqui está um exemplo dessa implementação bastante comum:
Imagine o que acontece com -initWithLessParameters, se eu fizer isso na subclasse:
Isso implica que você deve usar métodos privados (ocultos), especialmente em métodos de inicialização, a menos que planeje substituir os métodos. Mas esse é outro tópico, pois nem sempre você tem controle total na implementação da superclasse. (Isso me faz questionar o uso de __attribute ((objc_designated_initializer)) como uma prática ruim, embora eu não o tenha usado em profundidade.
Isso também implica que você pode usar asserções e exceções em métodos que devem ser substituídos nas subclasses. (Os métodos "abstratos", como em Criando uma classe abstrata em Objective-C )
E não se esqueça do método + new class.
fonte