Criando uma classe abstrata no Objective-C

507

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?

Jonathan Arbogast
fonte
18
As respostas abaixo são ótimas. Acho que o problema das classes abstratas está relacionado tangencialmente aos métodos privados - ambos são métodos para restringir o que o código do cliente pode fazer e nem existem no Objective-C. Eu acho que ajuda a entender que a mentalidade da própria linguagem é fundamentalmente diferente de Java. Veja a minha resposta para: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor
Obrigado pela informação sobre a mentalidade da comunidade Objective-C, em oposição a outros idiomas. Isso realmente resolve várias questões relacionadas que eu tinha (como por que não existe um mecanismo direto para métodos particulares, etc.).
23611 Jonathan Arbogast
1
então dê uma olhada no local CocoaDev o que lhe confere uma comparação java cocoadev.com/index.pl?AbstractSuperClass
2
Embora Barry o mencione como uma reflexão tardia (desculpe-me se estiver lendo errado), acho que você está procurando um protocolo no objetivo C. Veja, por exemplo, o que é um protocolo? .
JWW

Respostas:

633

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:

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

Se seu método retornar um valor, é um pouco mais fácil de usar

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

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.

Barry Wark
fonte
18
Acho que a parte mais apropriada da resposta foi mencionar que você poderia usar o @protocol para definir apenas métodos.
Chad Stewart
13
Para esclarecer: Você pode declarar métodos em uma @protocoldefinição, mas não pode definir os métodos lá.
Richard
Com um método de categoria no NSException, + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorisso funciona muito bem.
Patrick Pijnappel
Isso funcionou para mim, pois, no IMHO, lançar uma exceção é mais óbvio para outros desenvolvedores que esse é um comportamento desejado mais do que doesNotRecognizeSelector.
Chris
Use protocolos (para classe completamente abstrata) ou padrão de método de modelo em que a classe abstrata tenha lógica de implementação / fluxo parcial, conforme indicado aqui stackoverflow.com/questions/8146439/… . Veja minha resposta abaixo.
precisa saber é o seguinte
267

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:

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Você também pode fazer isso no init.

Grouchal
fonte
5
@ Chuck, não reduzi o voto, mas a NSObjectreferê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 :)
Dan Rosenstark
Por que você não gostaria de usar apenas um protocolo nesta instância? Para mim, é bom saber apenas para stubs de métodos, mas não para toda uma classe abstrata. O caso de uso para isso parece limitado.
lewiguez
Não diminuí a votação, mas a sugestão de que você deva causar uma exceção no método init é a razão mais provável. O formato mais comum para uma subclasse iniciará seu próprio método init chamando self = [super init] - que, obedientemente, lançará uma exceção. Esse formato funciona bem para a maioria dos métodos, mas eu nunca faria isso em nada em que a subclasse possa chamar de super implementação.
Xono
5
Você absolutamente pode criar classes abstratas no Objective-C, e isso é bastante comum. Existem várias classes de estrutura da Apple que fazem isso. As classes abstratas da Apple lançam exceções específicas (NSInvalidArgumentException), geralmente chamando NSInvalidAbstractInvocation (). Chamar um método abstrato é um erro de programação, e é por isso que gera uma exceção. Fábricas abstratas são comumente implementadas como clusters de classes.
quellish
2
@ quellish: Como você disse: chamar um método abstrato é um erro de programação. Ele deve ser tratado como tal, em vez de depender do relatório de erros em tempo de execução (NSException). Acho que esse é o maior problema para desenvolvedores vindos de outras linguagens, onde abstrato significa "não é possível instanciar um objeto desse tipo" no Obj-C, significa "as coisas vão dar errado no tempo de execução quando você instancia esta classe".
Cross_ 07/07
60

Apenas consultando a resposta de @Barry Wark acima (e atualizando para o iOS 4.3) e deixando isso para minha própria referência:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

em seus métodos, você pode usar isso

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



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 de NSInternalInconsistencyException), já que é assim que o sistema de tempo de execução lança em resposta a doesNotRecognizeSelectorser chamado (consulte a NSObjectdocumentação).

Dan Rosenstark
fonte
1
Claro, @ TomA, espero que lhe dê idéias para outro código que você possa macro. Minha macro mais usada é uma referência simples a um singleton: o código diz, universe.thingmas se expande [Universe universe].thing. Divertimento grande, salvando milhares de cartas de código ...
Dan Rosenstark
Ótimo. Mudou um pouco, no entanto: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm
1
@Yar: Eu acho que não. Usamos em __PRETTY_FUNCTION__todo o lugar, por meio de uma macro DLog (...), conforme sugerido aqui: stackoverflow.com/a/969291/38557 .
noamtm
1
para obter mais detalhes - ##define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
0800 gbk
1
se você adicionar a classe base e, em seguida, herdar essa classe por exemplo 10 vezes e se esquecer de implementá-la em uma das classes, receberá uma mensagem com o nome da classe Base, que não será herdada, como 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 implementedonde HomeViewController herdou da Base - isso dará mais informações
gbk
42

A solução que eu criei é:

  1. Crie um protocolo para tudo o que você deseja na sua classe "abstrata"
  2. Crie uma classe base (ou talvez chame de abstrata) que implemente o protocolo. Para todos os métodos que você deseja "abstrato", implemente-os no arquivo .m, mas não no arquivo .h.
  3. Faça com que sua classe filho herde da classe base E implemente o protocolo.

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.

redfood
fonte
2
+1 Esta é realmente a solução que mais se aproxima de uma classe abstrata em Java. Eu mesmo usei essa abordagem e funciona muito bem. É até permitido nomear o protocolo da mesma forma que a classe base (assim como a Apple fez 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.
CodigoFriend1
Ah, mas minha classe abstrata implementa parte de um protocolo, o resto é implementado por subclasses.
Roger CS Wernersson
1
você poderia fazer com que apenas as classes filho implementassem o protocolo e deixassem os métodos da superclasse todos juntos, em vez de em branco. em seguida, tenha propriedades do tipo Superclasse <MyProtocol>. Além disso, para aumentar a flexibilidade, você pode prefixar seus métodos no seu protocolo com @optional.
dotToString
Isso não parece funcionar no Xcode 5.0.2; ele gera apenas um aviso para a classe "abstrata". Não estender a classe "abstract" gera o aviso apropriado do compilador, mas obviamente não permite que você herde os métodos.
Topher Fangio
Prefiro esta solução, mas realmente não gosto, não é realmente uma boa estrutura de código no projeto. // deve usar isso como tipo base para subclasses. typedef BaseClass <BaseClassProtocol> BASECLASS; É regra de apenas uma semana, eu não gosto.
Itachi
35

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...

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Também faço o seguinte para impedir a inicialização da classe abstrata por meio do inicializador padrão.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
Peter Mortensen
fonte
1
Eu não tinha pensado em usar -doesNotRecognizeSelector: e eu meio que gosto dessa abordagem de algumas maneiras. Alguém sabe como fazer com que o compilador emita um aviso para um método "abstrato" criado dessa maneira ou criando uma exceção? Isso seria fantástico ...
Quinn Taylor
26
A abordagem doesNotRecognizeSelector impede o padrão self = [super init] sugerido pela Apple.
dlinsin
1
@ David: Eu tenho certeza que todo o ponto é levantar uma exceção o mais rápido possível. Idealmente, deveria ser em tempo de compilação, mas como não há como fazer isso, eles se contentaram com uma exceção de tempo de execução. Isso é semelhante a uma falha de asserção, que nunca deve ser aumentada no código de produção. De fato, uma declaração (false) pode realmente ser melhor, pois é mais claro que esse código nunca deve ser executado. O usuário não pode fazer nada para corrigi-lo, o desenvolvedor deve corrigi-lo. Assim, levantar uma falha de exceção ou afirmação aqui parece uma boa ideia.
Senseful
2
Eu não acho que essa seja uma solução viável, pois as subclasses podem ter chamadas perfeitamente legítimas para [super init].
Raffi Khatchadourian
@dlinsin: Isso é exatamente o que você deseja quando a classe abstrata não deve ser inicializada via init. Suas subclasses presumivelmente sabem qual super método chamar, mas isso interrompe a chamada descuidada por "novo" ou "alocação / inicialização".
gnasher729
21

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:

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

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.

Ben Gotow
fonte
Para mim, esse parece ser o caminho apropriado para os casos em que você usaria a classe abstrata, pelo menos quando pretende significar "interface" realmente (como em C ++). Existem desvantagens ocultas nessa abordagem?
Febeling 12/04
1
@febeling, classes abstratas - pelo menos em Java - não são apenas interfaces. Eles também definem algum (ou mais) comportamento. Essa abordagem pode ser boa em alguns casos, no entanto.
Dan Rosenstark
Preciso de uma classe base para implementar certas funcionalidades que todas as minhas subclasses compartilham (removendo a duplicação), mas também preciso de um protocolo para outros métodos com os quais a classe básica não deve lidar (a parte abstrata). Então, eu preciso usar os dois, e é aí que pode ser complicado garantir que suas subclasses estejam se implementando adequadamente.
LightningStryk
19

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 @dynamicpropriedades, 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:

  1. Implemente todos os métodos abstratos na classe base.
  2. Faça essa implementação [self doesNotRecognizeSelector:_cmd];...
  3. … Seguido por __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”.
  4. Combine as etapas 2. e 3. em uma macro ou anote o -[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á:

// Definition:
#define D12_ABSTRACT_METHOD {\
 [self doesNotRecognizeSelector:_cmd]; \
 __builtin_unreachable(); \
}

// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString

#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD

#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
    if (aRange.location + aRange.length >= [self length])
        [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];

    unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
    [self getCharacters:buffer range:aRange];

    return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…

@end

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

  1. O trabalho é realizado de maneira eficiente e conveniente.
  2. É bastante fácil de entender. (Ok, isso __builtin_unreachable()pode surpreender as pessoas, mas também é fácil de entender.)
  3. Ele não pode ser removido nas compilações de versão sem gerar outros avisos ou erros do compilador - ao contrário de uma abordagem baseada em uma das macros de asserção.

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?

danyowdee
fonte
Esta é a minha nova solução favorita - a __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.
quer
12

Usando @propertye @dynamictambé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á um unrecognized selectorerro 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.

Cameron Spickert
fonte
7

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 @interfacetag da classe base, os métodos "abstract":

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

Dando um passo adiante, eu crio uma macro:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

Isso permite que você faça isso:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

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.

Richard Stelling
fonte
1
Eu não consegui entender. Eu apliquei a sua sugestão na minha classe base -init método e agora Xcode não me permite criar uma instância da classe herdada e um erro de tempo de compilação ocorre indisponíveis ... . Você poderia explicar mais?
23612
Verifique se você tem um -initmétodo em sua subclasse.
Richard Stelling
2
É o mesmo que NS_UNAVAILABLE, que acionará um erro toda vez que você tentar chamar o método marcado com esse atributo. Não vejo como isso pode ser usado na aula abstrata.
Ben Sinclair
7

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.

@protocol ProtocolName
// list of methods and properties
@end

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?

hadaytullah
fonte
6

Outra alternativa

Basta verificar a classe na classe Abstract e em Assert ou Exception, o que você quiser.

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

Isso elimina a necessidade de substituir init

bigkm
fonte
Seria eficiente retornar apenas zero? Ou isso causaria uma exceção / outro erro?
user2277872
Bem, isso é apenas para pegar programadores para usar a classe diretamente, o que eu acho que faz bom uso de uma declaração.
Big
5

(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):

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

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.

exemplo da dica

Ivan Dossev
fonte
4

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:

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end
Cruz_
fonte
3

Provavelmente, esse tipo de situação deve ocorrer apenas no tempo de desenvolvimento, portanto, isso pode funcionar:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}
juanjo
fonte
3

Você pode usar um método proposto por @Yar (com algumas modificações):

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

Aqui você receberá uma mensagem como:

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

Ou afirmação:

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

Nesse caso, você receberá:

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

Além disso, você pode usar protocolos e outras soluções - mas essa é uma das mais simples.

gbk
fonte
2

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.

Hussain Shabbir
fonte
1

Normalmente desabilito o método init em uma classe que quero abstrair:

- (instancetype)__unavailable init; // This is an abstract class.

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.

mahoukov
fonte
Mas estou recebendo erro quando chamo [super init] da classe derivada dessa classe abstrata. Como resolver isso?
Sahil Doshi
@SahilDoshi Suponho que essa é a desvantagem do uso desse método. Eu o uso quando não quero permitir que uma classe seja instanciada e nada herda dessa classe.
Mahoukov
Sim, eu entendi isso. mas sua solução ajudará a criar algo como a classe singleton, na qual não queremos que ninguém chame init. de acordo com meu conhecimento em caso de classe abstrata, alguma classe herdará isso. senão qual é o uso da classe abstrata.
Sahil Doshi
0

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 .

  1. Crie um protocolo para todos os métodos que não fazem sentido para serem definidos na classe base (abstrata), ou seja, eles precisam de implementações específicas nos filhos.
  2. Crie uma classe base (abstrata) que não implemente esse protocolo. Você pode adicionar a essa classe quaisquer outros métodos que façam sentido para ter uma implementação comum.
  3. Em todo lugar no seu projeto, se um filho de AbstractClassprecisar ser inserido ou produzido por algum método, digite-o como AbstractClass<Protocol>alternativa.

Como AbstractClassnão implementa Protocol, a única maneira de ter uma AbstractClass<Protocol>instância é subclassificando. Como AbstractClasssozinho 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 IGListSectionControllerque não implementa o protocolo IGListSectionType; no entanto, todo método que requer uma instância dessa classe, na verdade, solicita o tipo IGListSectionController<IGListSectionType>. Portanto, não há como usar um objeto do tipo IGListSectionControllerpara algo útil em sua estrutura.

Gobe
fonte
0

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

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end
YorkFish
fonte
0

Um exemplo simples de criação de uma classe abstrata

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end
Say2Manuj
fonte
-3

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.

user1709076
fonte