Sou originalmente um programador Java que agora trabalha com o Objective-C. Eu gostaria de criar uma classe abstrata, mas isso não parece ser possível no Objective-C. Isso é possível?
Caso contrário, quão perto de uma classe abstrata posso chegar no Objective-C?
objective-c
abstract-class
Jonathan Arbogast
fonte
fonte
Respostas:
Normalmente, a classe Objective-C é abstrata apenas por convenção - se o autor documenta uma classe como abstrata, apenas não a use sem subclassificá-la. Entretanto, não há imposição em tempo de compilação que evite a instanciação de uma classe abstrata. De fato, não há nada para impedir que um usuário forneça implementações de métodos abstratos por meio de uma categoria (ou seja, em tempo de execução). Você pode forçar um usuário a pelo menos substituir certos métodos, gerando uma exceção na implementação desses métodos em sua classe abstrata:
Se seu método retornar um valor, é um pouco mais fácil de usar
então você não precisa adicionar uma declaração de retorno do método
Se a classe abstrata é realmente uma interface (ou seja, não possui implementações concretas de métodos), o uso de um protocolo Objective-C é a opção mais apropriada.
fonte
@protocol
definição, mas não pode definir os métodos lá.+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
isso funciona muito bem.doesNotRecognizeSelector
.Não, não há como criar uma classe abstrata no Objective-C.
Você pode simular uma classe abstrata - fazendo com que os métodos / seletores chamem doesNotRecognizeSelector: e, portanto, crie uma exceção tornando a classe inutilizável.
Por exemplo:
Você também pode fazer isso no init.
fonte
NSObject
referência sugere usá-lo onde você NÃO deseja herdar um método, para não impor a substituição de um método. Embora esses podem ser a mesma coisa, talvez :)Apenas consultando a resposta de @Barry Wark acima (e atualizando para o iOS 4.3) e deixando isso para minha própria referência:
em seus métodos, você pode usar isso
Notas: Não tenho certeza se fazer uma macro parecer uma função C é uma boa ideia ou não, mas vou mantê-la até que seja indicado o contrário. Eu acho que é mais correto usar
NSInvalidArgumentException
(em vez deNSInternalInconsistencyException
), já que é assim que o sistema de tempo de execução lança em resposta adoesNotRecognizeSelector
ser chamado (consulte aNSObject
documentação).fonte
universe.thing
mas se expande[Universe universe].thing
. Divertimento grande, salvando milhares de cartas de código ...#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.__PRETTY_FUNCTION__
todo o lugar, por meio de uma macro DLog (...), conforme sugerido aqui: stackoverflow.com/a/969291/38557 .#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
no caso de eu ter proposto, você também obteráHomeViewController - method not implemented
onde HomeViewController herdou da Base - isso dará mais informaçõesA solução que eu criei é:
Dessa forma, o compilador emitirá um aviso para qualquer método no protocolo que não seja implementado pela sua classe filha.
Não é tão sucinto quanto em Java, mas você recebe o aviso do compilador desejado.
fonte
NSObject
). Se você colocar o protocolo e a declaração da classe base no mesmo arquivo de cabeçalho, é quase indistinguível de uma classe abstrata.Na lista de correspondência do Omni Group :
O Objective-C não possui a construção abstrata do compilador como Java no momento.
Portanto, tudo que você faz é definir a classe abstrata como qualquer outra classe normal e implementar stubs de métodos para os métodos abstratos que estão vazios ou relatam não suporte ao seletor. Por exemplo...
Também faço o seguinte para impedir a inicialização da classe abstrata por meio do inicializador padrão.
fonte
Em vez de tentar criar uma classe base abstrata, considere usar um protocolo (semelhante a uma interface Java). Isso permite definir um conjunto de métodos e aceitar todos os objetos que estejam em conformidade com o protocolo e implementar os métodos. Por exemplo, eu posso definir um protocolo de Operação e, em seguida, ter uma função como esta:
Onde op pode ser qualquer objeto que implemente o protocolo de operação.
Se você precisar que sua classe base abstrata faça mais do que simplesmente definir métodos, poderá criar uma classe Objective-C regular e impedir que ela seja instanciada. Apenas substitua a função init - (id) e faça com que ela retorne nulo ou afirme (false). Não é uma solução muito limpa, mas como o Objective-C é totalmente dinâmico, não há realmente um equivalente direto a uma classe base abstrata.
fonte
Esse tópico é meio antigo, e a maior parte do que eu quero compartilhar já está aqui.
No entanto, meu método favorito não é mencionado, e o AFAIK não possui suporte nativo no Clang atual, então aqui vou eu…
Primeiro, e principalmente (como outros já apontaram) as classes abstratas são algo muito incomum no Objective-C - geralmente usamos composição (às vezes por meio de delegação). Essa é provavelmente a razão pela qual esse recurso ainda não existe no idioma / compilador - além das
@dynamic
propriedades, que o IIRC foi adicionado no ObjC 2.0 que acompanha a introdução do CoreData.Mas, considerando que (após uma avaliação cuidadosa da sua situação!), Você chegou à conclusão de que delegação (ou composição em geral) não é adequada para resolver seu problema, eis como eu faço isso:
[self doesNotRecognizeSelector:_cmd];
...__builtin_unreachable();
silenciar o aviso que você receberá por métodos não nulos, informando que “o controle atingiu o final da função não nula sem retorno”.-[NSObject doesNotRecognizeSelector:]
uso__attribute__((__noreturn__))
em uma categoria sem implementação para não substituir a implementação original desse método e inclua o cabeçalho dessa categoria na PCH do seu projeto.Pessoalmente, eu prefiro a versão macro, pois isso permite que eu reduza o padrão conforme possível.
Aqui está:
Como você pode ver, a macro fornece a implementação completa dos métodos abstratos, reduzindo a quantidade necessária de clichê para um mínimo absoluto.
Uma opção ainda melhor seria pressionar a equipe Clang para fornecer um atributo de compilador para este caso, por meio de solicitações de recursos. (Melhor, porque isso também permitiria diagnósticos em tempo de compilação para os cenários em que você subclasse, por exemplo, NSIncrementalStore.)
Por que eu escolhi este método
__builtin_unreachable()
pode surpreender as pessoas, mas também é fácil de entender.)Esse último ponto precisa de uma explicação, eu acho:
Algumas pessoas (a maioria?) Retiram as asserções nas versões de lançamento. (Não concordo com esse hábito, mas isso é outra história ...) Falhar na implementação de um método necessário - no entanto - é ruim , terrível , errado e basicamente o fim do universo para o seu programa. Seu programa não pode funcionar corretamente nesse sentido porque é indefinido e o comportamento indefinido é a pior coisa de todos os tempos. Portanto, ser capaz de eliminar esses diagnósticos sem gerar novos diagnósticos seria completamente inaceitável.
Já é ruim o suficiente que você não possa obter diagnósticos em tempo de compilação adequados para esses erros de programador, e tenha que recorrer à descoberta em tempo de execução para esses erros, mas se você pode aplicar isso em versões de compilação, por que tentar ter uma classe abstrata no primeiro lugar?
fonte
__builtin_unreachable();
jóia faz esse trabalho perfeitamente. A macro faz a auto-documentação e o comportamento corresponde ao que acontece se você chamar um método ausente em um objeto.Usando
@property
e@dynamic
também pode funcionar. Se você declarar uma propriedade dinâmica e não fornecer uma implementação de método correspondente, tudo continuará sendo compilado sem avisos e você receberá umunrecognized selector
erro em tempo de execução se tentar acessá-la. Isso é basicamente a mesma coisa que chamar[self doesNotRecognizeSelector:_cmd]
, mas com muito menos digitação.fonte
No Xcode (usando clang etc), eu gosto de usar
__attribute__((unavailable(...)))
para marcar as classes abstratas, para que você receba um erro / aviso se tentar usá-lo.Ele fornece alguma proteção contra o uso acidental do método.
Exemplo
Na
@interface
tag da classe base, os métodos "abstract":Dando um passo adiante, eu crio uma macro:
Isso permite que você faça isso:
Como eu disse, isso não é uma proteção real do compilador, mas é tão bom quanto você entrar em uma linguagem que não suporta métodos abstratos.
fonte
-init
método em sua subclasse.A resposta para a pergunta está espalhada nos comentários nas respostas já fornecidas. Então, estou apenas resumindo e simplificando aqui.
Opção1: Protocolos
Se você deseja criar uma classe abstrata sem implementação, use 'Protocolos'. As classes que herdam um protocolo são obrigadas a implementar os métodos no protocolo.
Option2: Padrão do Método do Modelo
Se você deseja criar uma classe abstrata com implementação parcial como "Template Method Pattern", esta é a solução. Objective-C - Padrão de métodos de modelo?
fonte
Outra alternativa
Basta verificar a classe na classe Abstract e em Assert ou Exception, o que você quiser.
Isso elimina a necessidade de substituir
init
fonte
(mais uma sugestão relacionada)
Eu queria ter uma maneira de deixar o programador saber "não chamo do filho" e substituir completamente (no meu caso, ainda ofereço algumas funcionalidades padrão em nome do pai quando não estendidas):
A vantagem é que o programador verá a "substituição" na declaração e saberá que não deve estar ligando
[super ..]
.Concedido, é feio ter que definir tipos de retorno individuais para isso, mas serve como uma dica visual suficientemente boa e você não pode usar facilmente a parte "override_" em uma definição de subclasse.
Obviamente, uma classe ainda pode ter uma implementação padrão quando uma extensão é opcional. Mas, como as outras respostas dizem, implemente uma exceção em tempo de execução, quando apropriado, como nas classes abstratas (virtuais).
Seria bom ter dicas integradas do compilador como esta, até dicas para quando é melhor pré / postar a chamada do implemento do super, em vez de ter que procurar nos comentários / documentação ou ... assumir.
fonte
Se você está acostumado ao compilador que captura violações de instanciação abstrata em outros idiomas, o comportamento do Objective-C é decepcionante.
Como uma linguagem de ligação tardia, fica claro que o Objective-C não pode tomar decisões estáticas sobre se uma classe é realmente abstrata ou não (você pode adicionar funções em tempo de execução ...), mas para casos de uso típicos isso parece uma falha. Eu preferiria as instanciações impedidas simples do compilador de classes abstratas em vez de gerar um erro no tempo de execução.
Aqui está um padrão que estamos usando para obter esse tipo de verificação estática usando algumas técnicas para ocultar inicializadores:
fonte
Provavelmente, esse tipo de situação deve ocorrer apenas no tempo de desenvolvimento, portanto, isso pode funcionar:
fonte
Você pode usar um método proposto por @Yar (com algumas modificações):
Aqui você receberá uma mensagem como:
Ou afirmação:
Nesse caso, você receberá:
Além disso, você pode usar protocolos e outras soluções - mas essa é uma das mais simples.
fonte
O cacau não fornece nada chamado abstrato. Podemos criar um resumo de classe que é verificado apenas em tempo de execução e, em tempo de compilação, isso não é verificado.
fonte
Normalmente desabilito o método init em uma classe que quero abstrair:
Isso gerará um erro no tempo de compilação sempre que você chamar init nessa classe. Eu então uso métodos de classe para todo o resto.
O Objective-C não possui uma maneira integrada de declarar classes abstratas.
fonte
Mudando um pouco o que o @redfood sugeriu ao aplicar o comentário do @ dotToString, você realmente tem a solução adotada pelo IGListKit do Instagram .
AbstractClass
precisar ser inserido ou produzido por algum método, digite-o comoAbstractClass<Protocol>
alternativa.Como
AbstractClass
não implementaProtocol
, a única maneira de ter umaAbstractClass<Protocol>
instância é subclassificando. ComoAbstractClass
sozinho não pode ser usado em qualquer lugar do projeto, ele se torna abstrato.Obviamente, isso não impede que desenvolvedores não aconselhados adicionem novos métodos simplesmente
AbstractClass
, o que acabaria permitindo uma instância da classe abstrata (não mais).Exemplo do mundo real: o IGListKit possui uma classe base
IGListSectionController
que não implementa o protocoloIGListSectionType
; no entanto, todo método que requer uma instância dessa classe, na verdade, solicita o tipoIGListSectionController<IGListSectionType>
. Portanto, não há como usar um objeto do tipoIGListSectionController
para algo útil em sua estrutura.fonte
De fato, o Objective-C não possui classes abstratas, mas você pode usar Protocolos para obter o mesmo efeito. Aqui está a amostra:
CustomProtocol.h
TestProtocol.h
TestProtocol.m
fonte
Um exemplo simples de criação de uma classe abstrata
fonte
Você não pode simplesmente criar um delegado?
Um delegado é como uma classe base abstrata no sentido em que você diz quais funções precisam ser definidas, mas na verdade não as define.
Então, sempre que você implementa seu delegado (ou seja, classe abstrata), você é avisado pelo compilador sobre quais funções opcionais e obrigatórias você precisa definir para o comportamento.
Isso soa como uma classe base abstrata para mim.
fonte