Créer une classe abstraite dans Objective-C

Je suis à l’origine un programmeur Java qui travaille maintenant avec Objective-C. J’aimerais créer une classe abstraite, mais cela ne semble pas possible dans Objective-C. Est-ce possible?

Si non, à quelle distance d’un cours de résumé puis-je accéder à Objective-C?

    En règle générale, les classes Objective-C ne sont abstraites que par convention. Si l’auteur documente une classe comme étant abstraite, ne l’utilisez pas sans la sous-classer. Cependant, il n’y a pas d’application de compilation qui empêche l’instanciation d’une classe abstraite. En fait, rien n’empêche un utilisateur de fournir des implémentations de méthodes abstraites via une catégorie (par exemple, à l’exécution). Vous pouvez forcer un utilisateur à remplacer au moins certaines méthodes en générant une exception dans l’implémentation de ces méthodes dans votre classe abstraite:

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

    Si votre méthode retourne une valeur, elle est un peu plus facile à utiliser

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

    comme alors vous n’avez pas besoin d’append une déclaration de retour de la méthode.

    Si la classe abstraite est vraiment une interface (c’est-à-dire qu’elle n’a pas d’implémentation de méthode concrète), utiliser un protocole Objective-C est l’option la plus appropriée.

    Non, il n’y a aucun moyen de créer une classe abstraite dans Objective-C.

    Vous pouvez simuler une classe abstraite – en faisant en sorte que les méthodes / sélecteurs appellent doesNotRecognizeSelector: et donc déclenchez une exception rendant la classe inutilisable.

    Par exemple:

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

    Vous pouvez également faire cela pour init.

    Il suffit de consulter la réponse de @Barry Wark ci-dessus (et de mettre à jour pour iOS 4.3) et de laisser ceci pour ma propre référence:

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

    alors dans vos méthodes, vous pouvez utiliser cette

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

    Notes: Ne pas savoir si faire une macro ressemble à une fonction C est une bonne idée ou non, mais je la garde jusqu’à ce que je sois informé du contraire. Je pense qu’il est plus correct d’utiliser NSInvalidArgumentException (plutôt que NSInternalInconsistencyException ), car c’est ce que le système d’exécution lance en réponse à l’ doesNotRecognizeSelector de doesNotRecognizeSelector (voir la documentation de NSObject ).

    La solution que j’ai trouvée est la suivante:

    1. Créez un protocole pour tout ce que vous voulez dans votre classe “abstraite”
    2. Créez une classe de base (ou appelez-la peut-être abstraite) qui implémente le protocole. Pour toutes les méthodes que vous voulez “abstraite” implémentez-les dans le fichier .m, mais pas le fichier .h.
    3. Demandez à votre classe enfant d’hériter de la classe de base ET implémentez le protocole.

    De cette façon, le compilateur vous avertira de toutes les méthodes du protocole qui ne sont pas implémentées par votre classe enfant.

    Ce n’est pas aussi succinct qu’en Java, mais vous obtenez l’avertissement du compilateur souhaité.

    De la liste de diffusion Omni Group :

    Objective-C n’a pas le compilateur abstrait construit comme Java pour le moment.

    Donc, tout ce que vous faites est de définir la classe abstraite comme toute autre classe normale et d’implémenter des méthodes stubs pour les méthodes abstraites qui sont vides ou qui signalent l’absence de prise en charge du sélecteur. Par exemple…

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

    Je fais également ce qui suit pour empêcher l’initialisation de la classe abstraite via l’initialiseur par défaut.

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

    Au lieu d’essayer de créer une classe de base abstraite, envisagez d’utiliser un protocole (similaire à une interface Java). Cela vous permet de définir un ensemble de méthodes, puis d’accepter tous les objects conformes au protocole et d’implémenter les méthodes. Par exemple, je peux définir un protocole d’opération, puis avoir une fonction comme celle-ci:

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

    Où op peut être n’importe quel object implémentant le protocole d’opération.

    Si vous avez besoin de votre classe de base abstraite pour faire plus que simplement définir des méthodes, vous pouvez créer une classe Objective-C régulière et l’empêcher d’être instanciée. Il suffit de remplacer la fonction init (id) et de la rendre nulle ou affirmée (false). Ce n’est pas une solution très propre, mais puisque Objective-C est entièrement dynamic, il n’y a pas vraiment d’équivalent direct avec une classe de base abstraite.

    Ce sujet est un peu ancien et la plupart de ce que je veux partager est déjà là.

    Cependant, ma méthode préférée n’est pas mentionnée et AFAIK n’a pas de support natif dans le Clang actuel, alors je vais…

    Tout d’abord (comme d’autres l’ont déjà fait remarquer), les classes abstraites sont quelque chose de très rare en Objective-C: nous utilisons généralement la composition (parfois par délégation). C’est probablement la raison pour laquelle une telle fonctionnalité n’existe pas déjà dans le langage / le compilateur – hormis les propriétés @dynamic , que IIRC a ajouté dans ObjC 2.0 accompagnant l’introduction de CoreData.

    Mais étant donné que (après une évaluation minutieuse de votre situation!) Vous êtes arrivé à la conclusion que la délégation (ou la composition en général) ne convient pas à la résolution de votre problème, voici comment je le fais:

    1. Implémentez chaque méthode abstraite dans la classe de base.
    2. Faites que cette implémentation [self doesNotRecognizeSelector:_cmd];
    3. … Suivi de __builtin_unreachable(); pour faire taire l’avertissement que vous obtenez pour les méthodes non-vides, en vous disant «le contrôle a atteint la fin de la fonction non-vide sans retour».
    4. Soit combiner les étapes 2. et 3. dans une macro, soit annoter -[NSObject doesNotRecognizeSelector:] utilisant __atsortingbute__((__noreturn__)) dans une catégorie sans implémentation afin de ne pas remplacer l’implémentation d’origine de cette méthode et d’inclure l’en-tête catégorie dans PCH de votre projet.

    Personnellement, je préfère la version macro car cela me permet de réduire le plus possible le mode opératoire.

    C’est ici:

     // Definition: #define D12_ABSTRACT_METHOD {\ [self doesNotRecognizeSelector:_cmd]; \ __builtin_unreachable(); \ } // Usage (assuming we were Apple, implementing the abstract base class NSSsortingng): @implementation NSSsortingng #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 - (NSSsortingng *)subssortingngWithRange:(NSRange)aRange { if (aRange.location + aRange.length >= [self length]) [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSSsortingngFromRange(aRange), [super description], (unsigned long)[self length]]; unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar)); [self getCharacters:buffer range:aRange]; return [[[NSSsortingng alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease]; } // and so forth… @end 

    Comme vous pouvez le constater, la macro fournit la mise en œuvre complète des méthodes abstraites, réduisant ainsi la quantité nécessaire de passe-partout à un minimum absolu.

    Une option encore meilleure serait de faire pression sur l’ équipe de Clang pour qu’elle fournisse un atsortingbut de compilateur pour ce cas, via des requêtes de fonctionnalités. (Mieux, car cela permettrait également des diagnostics à la compilation pour les scénarios dans lesquels vous sous-classe, par exemple, NSIncrementalStore.)

    Pourquoi je choisis cette méthode

    1. Le travail est fait de manière efficace et assez pratique.
    2. C’est assez facile à comprendre. (D’accord, __builtin_unreachable() peut surprendre les gens, mais c’est assez facile à comprendre aussi.)
    3. Il ne peut pas être supprimé dans les versions de publication sans générer d’autres avertissements ou erreurs de compilation, contrairement à une approche basée sur l’une des macros d’assertion.

    Ce dernier point nécessite des explications, je suppose:

    Certains (la plupart?) Des personnes éliminent les assertions dans les versions de publication. (Je ne suis pas d’accord avec cette habitude, mais c’est une autre histoire…) L’omission de mettre en œuvre une méthode requirejse – cependant – est mauvaise , terrible , fausse et, fondamentalement, la fin de l’univers de votre programme. Votre programme ne peut pas fonctionner correctement à cet égard car il est indéfini et que le comportement non défini est la pire des choses. Par conséquent, être capable de supprimer ces diagnostics sans générer de nouveaux diagnostics serait totalement inacceptable.

    Vous ne pouvez pas obtenir de diagnostics corrects à la compilation pour de telles erreurs de programmeur, et vous devez avoir recours à la découverte au moment de l’exécution pour ces erreurs, mais si vous pouvez les copier dans les versions, essayez d’avoir une classe abstraite dans le programme. première place?

    Utiliser @property et @dynamic pourrait également fonctionner. Si vous déclarez une propriété dynamic et que vous n’implémentez pas d’implémentation de méthode, tout sera toujours compilé sans avertissements, et vous obtiendrez une erreur de unrecognized selector à l’exécution si vous tentez d’y accéder. Cela [self doesNotRecognizeSelector:_cmd] essentiellement à appeler [self doesNotRecognizeSelector:_cmd] , mais avec beaucoup moins de frappe.

    Dans Xcode (en utilisant clang, etc.), j’aime bien utiliser __atsortingbute__((unavailable(...))) pour baliser les classes abstraites afin d’obtenir une erreur / un avertissement si vous essayez de l’utiliser.

    Il offre une protection contre l’utilisation accidentelle de la méthode.

    Exemple

    Dans la classe de base @interface liez les méthodes “abstract”:

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

    En allant plus loin, je crée une macro:

     #define UnavailableMacro(msg) __atsortingbute__((unavailable(msg))) 

    Cela vous permet de faire ceci:

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

    Comme je l’ai dit, ce n’est pas une véritable protection du compilateur, mais c’est à peu près tout ce que vous allez faire dans un langage qui ne supporte pas les méthodes abstraites.

    La réponse à la question est dispersée dans les commentaires sous les réponses déjà données. Donc, je résume simplement et simplifie ici.

    Option 1: Protocoles

    Si vous voulez créer une classe abstraite sans implémentation, utilisez ‘Protocols’. Les classes héritant d’un protocole sont obligées d’implémenter les méthodes du protocole.

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

    Option 2: Modèle de méthode de modèle

    Si vous voulez créer une classe abstraite avec une implémentation partielle telle que “Template Method Pattern”, alors c’est la solution. Objective-C – Modèle de méthode de modèle?

    Une autre alternative

    Vérifiez simplement la classe dans la classe Abstract et Assert ou Exception, quel que soit votre choix.

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

    Cela supprime la nécessité de remplacer init

    (plus d’une suggestion connexe)

    Je voulais avoir un moyen de faire savoir au programmeur “ne pas appeler depuis l’enfant” et de remplacer complètement (dans mon cas, encore offrir des fonctionnalités par défaut pour le compte du parent lorsqu’il n’est pas étendu):

     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 

    L’avantage est que le programmeur verra le “remplacement” dans la déclaration et saura qu’il ne devrait pas appeler [super ..] .

    Certes, c’est moche de devoir définir des types de retour individuels pour cela, mais cela sert de bon indice visuel et vous ne pouvez pas facilement utiliser la partie “override_” dans une définition de sous-classe.

    Bien sûr, une classe peut toujours avoir une implémentation par défaut lorsqu’une extension est facultative. Mais, comme le disent les autres réponses, implémentez une exception d’exécution, le cas échéant, comme pour les classes abstraites (virtuelles).

    Ce serait bien d’avoir des astuces de compilateur comme celle-ci, même des astuces pour savoir quand il est préférable de pré-appeler les super-implémentations, plutôt que de devoir fouiller dans les commentaires / la documentation ou … supposer.

    exemple de l'indice

    Si vous avez l’habitude que le compilateur détecte des violations d’instanciation abstraites dans d’autres langages, le comportement d’Objective-C est décevant.

    En tant que langage de liaison tardif, il est clair qu’Objective-C ne peut pas prendre de décisions statiques sur le fait qu’une classe est vraiment abstraite ou non (vous appendez peut-être des fonctions au moment de l’exécution), mais cela semble être une lacune. Je préférerais que le compilateur a évité les instanciations de classes abstraites au lieu de lancer une erreur à l’exécution.

    Voici un modèle que nous utilisons pour obtenir ce type de vérification statique en utilisant quelques techniques pour masquer les initialiseurs:

     // // Base.h #define UNAVAILABLE __atsortingbute__((unavailable("Default initializer not available."))); @protocol MyProtocol  -(void) dependentFunction; @end @interface Base : NSObject { @protected __weak id _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 ()  @end @implementation Derived -(instancetype) initDerived { self= [super initFromDerived]; if (self) { self->_protocolHelper= self; } return self; } // Implement the missing function -(void)dependentFunction { } @end 

    Ce genre de situation ne devrait probablement se produire qu’au moment du développement, donc cela pourrait fonctionner:

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

    Vous pouvez utiliser une méthode proposée par @Yar (avec quelques modifications):

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

    Ici vous aurez un message comme:

      ProjectName[7921:1967092]  - method not implemented  ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ ] must be overridden in a subclass/category' 

    Ou assertion:

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

    Dans ce cas, vous obtiendrez:

      ProjectName[7926:1967491] *** Assertion failure in -[ ], /Users/kirill/Documents/Projects/root/ Services/Classes/ViewControllers/YourClass:53 

    Vous pouvez également utiliser des protocoles et d’autres solutions – mais c’est l’une des plus simples.

    Le cacao ne fournit rien qui s’appelle abstrait. Nous pouvons créer un résumé de classe qui est vérifié uniquement au moment de l’exécution, et au moment de la compilation, cela n’est pas vérifié.

    Je ne fais que désactiver la méthode init dans une classe que je veux résumer:

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

    Cela générera une erreur au moment de la compilation chaque fois que vous appelez init sur cette classe. J’utilise ensuite des méthodes de classe pour tout le rest.

    Objective-C n’a pas de moyen intégré pour déclarer des classes abstraites.

    En changeant un peu ce que @redfood a suggéré en appliquant le commentaire de @ dotToSsortingng, vous avez en fait adopté la solution par IGListKit sur Instagram.

    1. Créez un protocole pour toutes les méthodes qui n’ont aucun sens à définir dans la classe de base (abstraite), c’est-à-dire qu’elles nécessitent des implémentations spécifiques chez les enfants.
    2. Créez une classe de base (abstraite) qui n’implémente pas ce protocole. Vous pouvez append à cette classe toutes les autres méthodes utiles pour une implémentation commune.
    3. Partout dans votre projet, si un enfant de AbstractClass doit être entré ou sorti par une méthode, saisissez-le plutôt comme AbstractClass .

    Parce que AbstractClass n’implémente pas le Protocol , la seule manière d’avoir une instance de AbstractClass est de sous-classer. Comme AbstractClass seul ne peut être utilisé nulle part dans le projet, il devient abstrait.

    Bien sûr, cela n’empêche pas les développeurs non conseillés d’append de nouvelles méthodes faisant simplement référence à AbstractClass , ce qui finirait par permettre une instance de la classe abstraite (plus).

    Exemple concret : IGListKit a une classe de base IGListSectionController qui IGListSectionController pas le protocole IGListSectionType . Cependant, chaque méthode nécessitant une instance de cette classe demande en fait le type IGListSectionController . Il n’y a donc aucun moyen d’utiliser un object de type IGListSectionController pour quelque chose d’utile dans leur framework.

    En fait, Objective-C n’a pas de classes abstraites, mais vous pouvez utiliser les protocoles pour obtenir le même effet. Voici l’échantillon:

    CustomProtocol.h

     #import  @protocol CustomProtocol  @required - (void)methodA; @optional - (void)methodB; @end 

    TestProtocol.h

     #import  #import "CustomProtocol.h" @interface TestProtocol : NSObject  @end 

    TestProtocol.m

     #import "TestProtocol.h" @implementation TestProtocol - (void)methodA { NSLog(@"methodA..."); } - (void)methodB { NSLog(@"methodB..."); } @end 

    Un exemple simple de création d’une classe abstraite

     // Declare a protocol @protocol AbcProtocol  -(void)fnOne; -(void)fnTwo; @optional -(void)fnThree; @end // Abstract class @interface AbstractAbc : NSObject @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 

    Ne pouvez-vous pas simplement créer un délégué?

    Un délégué est comme une classe de base abstraite dans le sens où vous dites quelles fonctions doivent être définies, mais vous ne les définissez pas réellement.

    Ensuite, chaque fois que vous implémentez votre délégué (c’est-à-dire la classe abstraite), le compilateur vous avertit des fonctions facultatives et obligatoires pour lesquelles vous devez définir un comportement.

    Cela ressemble à une classe de base abstraite pour moi.