Question 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 en Objective-C. Est-ce possible?

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


496
2018-06-23 18:42


origine


Réponses:


Généralement, la classe Objective-C est abstraite par convention uniquement - si l'auteur documente une classe comme abstraite, ne l'utilisez pas sans la sous-classer. Cependant, il n'y a pas d'application à la 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 (c'est-à-dire au moment de 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", NSStringFromSelector(_cmd)];

Si votre méthode renvoie une valeur, il est un peu plus facile à utiliser

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

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

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


624
2018-06-23 18:56



Non, il n'y a aucun moyen de créer une classe abstraite en 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.


267
2018-06-23 18:47



Juste riff sur la réponse de @Barry Wark ci-dessus (et mise à jour pour iOS 4.3) et en laissant cela pour ma propre référence:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%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
}



Remarques: Je ne sais pas si faire une macro ressemble à une fonction C est une bonne idée ou non, mais je vais le garder jusqu'à l'école du contraire. Je pense qu'il est plus correct d'utiliser NSInvalidArgumentException (plutôt que NSInternalInconsistencyException) puisque c'est ce que le système d'exécution jette en réponse à doesNotRecognizeSelector être appelé (voir NSObject docs).


60
2018-06-23 02:39



La solution que j'ai trouvée est:

  1. Créez un protocole pour tout ce que vous voulez dans votre classe "abstraite"
  2. Créez une classe de base (ou peut-être appelez-la abstract) qui implémente le protocole. Pour toutes les méthodes que vous voulez "abstrait" les implémenter dans le fichier .m, mais pas le fichier .h.
  3. Faites en sorte que votre classe enfant hérite de la classe de base ET implémente le protocole.

De cette façon, le compilateur vous avertira de toute méthode du protocole qui n'est pas implémentée par votre classe enfant.

Ce n'est pas aussi succinct que dans Java, mais vous obtenez l'avertissement du compilateur désiré.


40
2018-06-21 19:03



Du Liste de diffusion d'Omni Group:

Objective-C n'a pas la construction du compilateur abstrait comme Java à cette fois.

Donc tout ce que vous faites est de définir la classe abstraite comme n'importe quelle autre classe normale et implémenter des talons de méthodes pour les méthodes abstraites qui sont soit vider ou signaler l'absence de prise en charge du sélecteur. Par exemple...

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

Je fais aussi ce qui suit pour empêcher l'initialisation du résumé class via l'initialiseur par défaut.

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

34
2018-06-23 18:45



Au lieu d'essayer de créer une classe de base abstraite, pensez à utiliser un protocole (similaire à une interface Java). Cela vous permet de définir un ensemble de méthodes, puis d'accepter tous les objets 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<Operation>)op
{
   // do something with operation
}

Où op peut être n'importe quel objet 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 comme Objective-C est entièrement dynamique, il n'y a pas d'équivalent direct avec une classe de base abstraite.


21
2018-06-23 18:57



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 la version actuelle de Clang, alors voilà…

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 - à part @dynamic propriétés, qui IIRC ont été ajoutés 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 fais le:

  1. Implémentez chaque méthode abstraite dans la classe de base.
  2. Faire cette implémentation [self doesNotRecognizeSelector:_cmd];...
  3. …suivi par __builtin_unreachable(); faire taire l'avertissement que vous obtiendrez pour les méthodes non-nulles, en vous disant "le contrôle a atteint la fin de la fonction non-vide sans retour".
  4. Combinez les étapes 2. et 3. dans une macro ou annotez -[NSObject doesNotRecognizeSelector:] en utilisant __attribute__((__noreturn__)) dans une catégorie sans mise en œuvre afin de ne pas remplacer l'implémentation d'origine de cette méthode et inclure l'en-tête de cette 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 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

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  Équipe de clang pour fournir un attribut de compilateur pour ce cas, via des demandes de fonctionnalités. (Mieux, car cela activerait également les diagnostics à la compilation pour les scénarios dans lesquels vous sous-classe, par exemple, NSIncrementalStore.)

Pourquoi je choisis cette méthode

  1. C'est faire le travail efficacement, et un peu commodément.
  2. C'est assez facile à comprendre. (Ok, ça __builtin_unreachable() peut surprendre les gens, mais c'est assez facile à comprendre aussi.)
  3. Il ne peut pas être supprimé dans les versions de compilation 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:

Certaines (la plupart?) Dépouillent les assertions dans les versions de versions. (Je suis en désaccord avec cette habitude, mais c'est une autre histoire ...) Ne pas mettre en œuvre une méthode requise - cependant - est mal, terrible, faux, et essentiellement la fin de l'univers pour votre programme. Votre programme ne peut pas fonctionner correctement à cet égard car il est indéfini et un comportement indéfini est la pire chose qui soit. Par conséquent, être capable de supprimer ces diagnostics sans générer de nouveaux diagnostics serait totalement inacceptable.

Il est déjà assez difficile de ne pas obtenir de diagnostics de compilation appropriés pour de telles erreurs de programmeur, et d'avoir recours à une découverte au moment de l'exécution, mais si vous pouvez l'utiliser dans les builds de versions, essayez d'avoir une classe abstraite première place?


18
2017-07-23 18:20



En utilisant @property et @dynamic pourrait aussi fonctionner. Si vous déclarez une propriété dynamique et ne donnez pas une implémentation de méthode correspondante, tout compilera toujours sans avertissement, et vous obtiendrez une unrecognized selector erreur lors de l'exécution si vous essayez d'y accéder. C'est essentiellement la même chose que d'appeler [self doesNotRecognizeSelector:_cmd], mais avec beaucoup moins de frappe.


12
2017-11-03 21:46



Dans Xcode (en utilisant clang etc) j'aime utiliser __attribute__((unavailable(...))) pour marquer les classes abstraites afin d'obtenir une erreur / avertissement si vous essayez de l'utiliser.

Il fournit une certaine protection contre l'utilisation accidentelle de la méthode.

Exemple

Dans la classe de base @interface étiqueter les méthodes "abstraites":

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

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

#define UnavailableMacro(msg) __attribute__((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 vraie protection de compilateur, mais c'est à peu près aussi bon que d'aller dans un langage qui ne supporte pas les méthodes abstraites.


7
2017-11-06 13:38



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 dans le 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éthodes de modèle?


7
2018-03-27 10:59