Ce fil 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 il n'y a pas de support natif dans le Clang actuel, alors c'est parti…
Tout d'abord, et surtout (comme d'autres l'ont déjà souligné), les classes abstraites sont quelque chose de très rare dans Objective-C - nous utilisons généralement la composition (parfois par délégation) à la place. C'est probablement la raison pour laquelle une telle fonctionnalité n'existe pas déjà dans le langage / compilateur - à part les @dynamic
propriétés, que l'IIRC a ajoutées 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) n'est pas bien adaptée pour résoudre votre problème, voici comment je le fais:
- Implémentez chaque méthode abstraite dans la classe de base.
- Faites cette mise en œuvre
[self doesNotRecognizeSelector:_cmd];
…
- … Suivi de
__builtin_unreachable();
pour faire taire l'avertissement que vous obtiendrez pour les méthodes non nulles, vous indiquant «le contrôle a atteint la fin de la fonction non nulle sans retour».
- Vous pouvez soit combiner les étapes 2. et 3. dans une macro, soit annoter l'
-[NSObject doesNotRecognizeSelector:]
utilisation __attribute__((__noreturn__))
dans une catégorie sans implémentation 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 le PCH de votre projet.
Personnellement, je préfère la version macro car cela me permet de réduire le plus possible le passe-partout.
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 voir, la macro fournit l'implémentation complète des méthodes abstraites, réduisant la quantité nécessaire de passe-partout au minimum absolu.
Une option encore meilleure serait de faire pression sur l' équipe Clang pour fournir un attribut de compilateur pour ce cas, via des demandes de fonctionnalités. (Mieux, car cela permettrait également des diagnostics au moment de la compilation pour les scénarios où vous sous-classe, par exemple NSIncrementalStore.)
Pourquoi je choisis cette méthode
- Il fait le travail de manière efficace et quelque peu pratique.
- C'est assez facile à comprendre. (D'accord, cela
__builtin_unreachable()
peut surprendre les gens, mais c'est assez facile à comprendre aussi.)
- Il ne peut pas être supprimé dans les versions de version sans générer d'autres avertissements ou erreurs du compilateur - contrairement à une approche basée sur l'une des macros d'assertion.
Ce dernier point nécessite quelques explications, je suppose:
Certaines personnes (la plupart?) Suppriment les assertions dans les versions. (Je ne suis pas d'accord avec cette habitude, mais c'est une autre histoire…) Le fait de ne pas implémenter une méthode requise - cependant - est mauvais , terrible , mauvais et, fondamentalement, la fin de l'univers pour votre programme. Votre programme ne peut pas fonctionner correctement à cet égard car il n'est pas défini et un comportement indéfini est la pire chose qui soit. Par conséquent, être en mesure de supprimer ces diagnostics sans générer de nouveaux diagnostics serait totalement inacceptable.
C'est déjà assez grave pour que vous ne puissiez pas obtenir de diagnostics de compilation corrects pour ces erreurs de programmation, et que vous ayez à recourir à la découverte au moment de l'exécution pour celles-ci, mais si vous pouvez les recouvrir dans les versions, pourquoi essayer d'avoir une classe abstraite dans le première place?