Création d'une classe abstraite dans Objective-C

507

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

Sinon, à quelle distance d'une classe abstraite puis-je obtenir en Objective-C?

Jonathan Arbogast
la source
18
Les réponses ci-dessous sont excellentes. Je trouve que le problème des classes abstraites est tangentiellement lié aux méthodes privées - les deux sont des méthodes pour restreindre ce que le code client peut faire, et aucune n'existe en Objective-C. Je pense que cela aide à comprendre que la mentalité du langage lui-même est fondamentalement différente de Java. Voir ma réponse à: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor
Merci pour l'info sur la mentalité de la communauté Objective-C par rapport aux autres langues. Cela résout vraiment un certain nombre de questions connexes que j'avais (comme pourquoi aucun mécanisme simple pour les méthodes privées, etc.).
Jonathan Arbogast
1
alors jetez un œil au site CocoaDev qui lui donne une comparaison java cocoadev.com/index.pl?AbstractSuperClass
2
Bien que Barry le mentionne après coup (pardonnez-moi si je le lis mal), je pense que vous cherchez un protocole dans l'objectif C. Voir, par exemple, Qu'est-ce qu'un protocole? .
jww

Réponses:

633

En règle générale, les classes Objective-C sont abstraites 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 au moment de 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 levant 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 retourne une valeur, c'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 vraiment une interface (c'est-à-dire qu'elle 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.

Barry Wark
la source
18
Je pense que la partie la plus appropriée de la réponse mentionnait que vous pouviez utiliser @protocol à la place pour simplement définir des méthodes.
Chad Stewart
13
Pour clarifier: vous pouvez déclarer des méthodes dans une @protocoldéfinition, mais vous ne pouvez pas y définir les méthodes.
Richard
Avec une méthode de catégorie sur NSException, + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorcela fonctionne très bien.
Patrick Pijnappel
Cela a fonctionné pour moi car, à mon humble avis, lever une exception est plus évident pour les autres développeurs que c'est un comportement souhaité plus que doesNotRecognizeSelector.
Chris
Utilisez des protocoles (pour une classe complètement abstraite) ou un modèle de méthode de modèle où la classe abstraite a une logique d'implémentation / flux partielle comme indiqué ici stackoverflow.com/questions/8146439/… . Voir ma réponse ci-dessous.
hadaytullah
267

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

Vous pouvez vous moquer d'une classe abstraite - en faisant appeler les méthodes / sélecteurs doesNotRecognizeSelector: et donc lever une exception rendant la classe inutilisable.

Par exemple:

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

Vous pouvez également le faire pour init.

Grouchal
la source
5
@Chuck, je n'ai pas downvote, mais la NSObjectréférence suggère d'utiliser ceci où vous ne voulez PAS hériter d'une méthode, pas pour forcer le remplacement d'une méthode. Bien que cela puisse être la même chose, peut-être :)
Dan Rosenstark
Pourquoi ne voudriez-vous pas simplement utiliser un protocole dans ce cas? Pour moi, c'est agréable à savoir pour les seuls stubs de méthode, mais pas pour toute une classe abstraite. Le cas d'utilisation pour cela semble limité.
lewiguez
Je ne l'ai pas dévalorisé, mais la suggestion que vous devriez provoquer une exception dans la méthode init est la raison la plus probable. Le format le plus courant pour une sous-classe démarrera sa propre méthode init en appelant self = [super init] - qui lèvera docilement une exception. Ce format fonctionne bien pour la plupart des méthodes, mais je ne le ferais jamais dans aucun domaine où la sous-classe pourrait appeler sa super implémentation.
Xono
5
Vous pouvez absolument créer des classes abstraites dans Objective-C, et c'est assez courant. Il existe un certain nombre de classes de framework Apple qui font cela. Les classes abstraites d'Apple lèvent des exceptions spécifiques (NSInvalidArgumentException), souvent en appelant NSInvalidAbstractInvocation (). L'appel d'une méthode abstraite est une erreur de programmation, c'est pourquoi elle lève une exception. Les usines abstraites sont généralement implémentées en tant que clusters de classes.
quellish
2
@quellish: Comme vous l'avez dit: appeler une méthode abstraite est une erreur de programmation. Il doit être traité comme tel, au lieu de s'appuyer sur le rapport d'erreurs d'exécution (NSException). Je pense que c'est le plus gros problème pour les développeurs venant d'autres langages où abstrait signifie "ne peut pas instancier un objet de ce type" dans Obj-C cela signifie "les choses vont mal au moment de l'exécution lorsque vous instanciez cette classe".
Cross_
60

Juste en riffant sur la réponse de @Barry Wark ci-dessus (et en mettant à 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()

puis 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'à ce que vous appreniez le 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' doesNotRecognizeSelectorappel (voir la NSObjectdocumentation).

Dan Rosenstark
la source
1
Bien sûr, @TomA, j'espère que cela vous donne des idées pour d'autres codes que vous pouvez macro. Ma macro la plus utilisée est une simple référence à un singleton: le code dit universe.thingmais il se développe [Universe universe].thing. Grand plaisir, sauver des milliers de lettres de code ...
Dan Rosenstark
Génial. Changé un peu, cependant: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm
1
@Yar: Je ne pense pas. Nous utilisons __PRETTY_FUNCTION__partout, via une macro DLog (...), comme suggéré ici: stackoverflow.com/a/969291/38557 .
noamtm
1
pour plus de détails -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk
1
si vous ajoutez la classe de base, puis héritez de cette classe par exemple 10 fois et oubliez de l'implémenter dans l'une des classes, vous obtiendrez un message avec le nom de la classe de base non hérité, comme Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'dans le cas où je propose, vous obtiendrez également HomeViewController - method not implementedoù HomeViewController hérité de Base - ce donnera plus d'informations
gbk
42

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 appelez-la peut-être abstraite) qui implémente le protocole. Pour toutes les méthodes que vous souhaitez "abstraites", implémentez-les dans le fichier .m, mais pas dans 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 donnera un avertissement pour toute méthode du protocole qui n'est pas implémentée par votre classe enfant.

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

redfood
la source
2
+1 C'est vraiment la solution qui se rapproche le plus d'une classe abstraite en Java. J'ai utilisé cette approche moi-même et cela fonctionne très bien. Il est même permis de nommer le protocole de la même manière que la classe de base (comme Apple l'a fait avec NSObject). Si vous placez ensuite le protocole et la déclaration de classe de base dans le même fichier d'en-tête, il est presque impossible de les distinguer d'une classe abstraite.
codingFriend1
Ah, mais ma classe abstraite implémente une partie d'un protocole, le reste est implémenté par des sous-classes.
Roger CS Wernersson
1
vous pourriez avoir seulement les classes enfants implémenter le protocole et laisser les méthodes de superclasse toutes ensemble au lieu d'être vides. ont alors des propriétés de type Superclasse <MonProtocole>. De plus, pour plus de flexibilité, vous pouvez préfixer vos méthodes dans votre protocole avec @optional.
dotToString
Cela ne semble pas fonctionner dans Xcode 5.0.2; il ne génère qu'un avertissement pour la classe "abstraite". Ne pas étendre la classe "abstraite" génère l'avertissement du compilateur approprié, mais ne vous laisse évidemment pas hériter des méthodes.
Topher Fangio
Je préfère cette solution mais je ne l'aime vraiment pas, ce n'est vraiment pas une bonne structure de code dans le projet. // devrait l'utiliser comme type de base pour les sous-classes. typedef BaseClass <BaseClassProtocol> BASECLASS; C'est juste une règle d'une semaine, je n'aime pas ça.
Itachi
35

À partir de la liste de diffusion Omni Group :

Objective-C n'a pas la construction du compilateur abstrait 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 talons de méthodes pour les méthodes abstraites qui sont vides ou signalent la non-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;
}
Peter Mortensen
la source
1
Je n'avais pas pensé à utiliser -doesNotRecognizeSelector: et j'aime un peu cette approche à certains égards. Quelqu'un connaît-il un moyen de faire émettre au compilateur un avertissement pour une méthode "abstraite" créée de cette façon ou en levant une exception? Ce serait génial ...
Quinn Taylor
26
L'approche doesNotRecognizeSelector empêche le modèle self = [super init] suggéré par Apple.
dlinsin
1
@david: Je suis à peu près sûr que le but est de lever une exception dès que possible. Idéalement, cela devrait être au moment de la compilation, mais comme il n'y a aucun moyen de le faire, ils se sont contentés d'une exception au moment de l'exécution. Cela s'apparente à un échec d'assertion, qui ne devrait jamais être soulevé dans le code de production en premier lieu. En fait, une assertion (false) pourrait en fait être meilleure car il est plus clair que ce code ne devrait jamais s'exécuter. L'utilisateur ne peut rien faire pour le réparer, le développeur doit le réparer. Ainsi, lever une exception ou un échec d'assertion semble être une bonne idée.
Senseful
2
Je ne pense pas que ce soit une solution viable car les sous-classes peuvent avoir des appels parfaitement légitimes à [super init].
Raffi Khatchadourian
@dlinsin: C'est exactement ce que vous voulez quand la classe abstraite n'est pas censée être initialisée via init. Ses sous-classes savent probablement quelle super méthode appeler, mais cela arrête l'invocation imprudente via "new" ou "alloc / init".
gnasher729
21

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 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 empêcher qu'elle ne soit instanciée. Remplacez simplement la fonction - (id) init et faites-la retourner nil ou assert (false). Ce n'est pas une solution très propre, mais comme Objective-C est entièrement dynamique, il n'y a vraiment pas d'équivalent direct à une classe de base abstraite.

Ben Gotow
la source
Pour moi, cela semble être la voie à suivre pour les cas où vous utiliseriez une classe abstraite, du moins quand cela signifie vraiment «interface» (comme en C ++). Y a-t-il des inconvénients cachés à cette approche?
febeling le
1
@febeling, les classes abstraites - du moins en Java - ne sont pas que des interfaces. Ils définissent également certains (ou la plupart) des comportements. Cette approche pourrait cependant être bonne dans certains cas.
Dan Rosenstark
J'ai besoin d'une classe de base pour implémenter certaines fonctionnalités que mes sous-classes partagent toutes (suppression de la duplication), mais j'ai également besoin d'un protocole pour d'autres méthodes que la classe de base ne devrait pas gérer (la partie abstraite). Je dois donc utiliser les deux, et c'est là que cela peut devenir difficile de s'assurer que vos sous-classes se mettent en œuvre correctement.
LightningStryk
19

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 @dynamicproprié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:

  1. Implémentez chaque méthode abstraite dans la classe de base.
  2. Faites cette mise en œuvre [self doesNotRecognizeSelector:_cmd];
  3. … 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».
  4. 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

  1. Il fait le travail de manière efficace et quelque peu pratique.
  2. C'est assez facile à comprendre. (D'accord, cela __builtin_unreachable()peut surprendre les gens, mais c'est assez facile à comprendre aussi.)
  3. 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?

danyowdee
la source
Ceci est ma nouvelle solution préférée - le __builtin_unreachable();bijou fait que cela fonctionne parfaitement. La macro la rend auto-documentée et le comportement correspond à ce qui se passe si vous appelez une méthode manquante sur un objet.
Alex MDC
12

Utilisation @propertyet @dynamicpourrait également fonctionner. Si vous déclarez une propriété dynamique et ne donnez pas d'implémentation de méthode correspondante, tout sera compilé sans avertissements et vous obtiendrez une unrecognized selectorerreur au moment 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.

Cameron Spickert
la source
7

Dans Xcode (en utilisant clang, etc.), j'aime utiliser __attribute__((unavailable(...)))pour baliser les classes abstraites afin que vous obteniez une erreur / un avertissement si vous essayez de l'utiliser.

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

Exemple

Dans la @interfacebalise de classe de base, 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 véritable protection du compilateur, mais c'est à peu près aussi efficace que vous allez obtenir dans un langage qui ne prend pas en charge les méthodes abstraites.

Richard Stelling
la source
1
Je n'ai pas pu l'obtenir. J'ai appliqué votre suggestion de ma classe de base -init méthode et maintenant Xcode ne me permet pas de créer une instance de la classe héritée et une erreur de compilation se produit indisponible ... . Pourriez-vous s'il vous plaît expliquer plus?
anonim
Assurez-vous d'avoir une -initméthode dans votre sous-classe.
Richard Stelling
2
C'est la même chose que NS_UNAVAILABLE qui déclenchera une erreur à chaque fois que vous essayerez d'appeler la méthode marquée avec un tel attribut. Je ne vois pas comment il peut être utilisé sur une classe abstraite.
Ben Sinclair
7

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

Option 1: protocoles

Si vous souhaitez créer une classe abstraite sans implémentation, utilisez «Protocoles». 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

Option2: modèle de méthode de modèle

Si vous voulez créer une classe abstraite avec une implémentation partielle comme "Pattern Method Pattern", alors c'est la solution. Objective-C - Modèle de méthodes de modèle?

hadaytullah
la source
6

Une autre alternative

Il vous suffit de vérifier la classe dans la classe Abstract et d'affirmer ou d'exclure ce que vous voulez.

@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 passer outre init

bigkm
la source
Serait-il efficace de simplement retourner zéro? Ou est-ce que cela provoquerait une exception / autre erreur?
user2277872
Eh bien, c'est juste pour attraper les programmeurs qui utilisent directement la classe, ce qui, je pense, fait bon usage d'une assertion.
bigkm
5

(plus d'une suggestion connexe)

Je voulais avoir un moyen de faire savoir au programmeur "ne pas appeler de l'enfant" et de passer outre complètement (dans mon cas, toujours offrir certaines fonctionnalités par défaut au nom 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 VOIRa le "remplacement" dans la déclaration et saura qu'il ne doit pas appeler [super ..].

Certes, il est moche d'avoir à 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 intégré des astuces de compilateur comme celle-ci, même des astuces pour quand il est préférable de pré / post appeler l'implémentation du super, au lieu d'avoir à fouiller dans les commentaires / documentation ou ... à supposer.

exemple de l'indice

Ivan Dossev
la source
4

Si vous êtes habitué à ce que le compilateur détecte des violations d'instanciation abstraites dans d'autres langues, le comportement d'Objective-C est décevant.

En tant que langage de liaison tardive, il est clair qu'Objective-C ne peut pas décider statiquement si une classe est vraiment abstraite ou non (vous pourriez ajouter des fonctions au moment de l'exécution ...), mais pour les cas d'utilisation typiques, cela semble être un défaut. Je préférerais que le compilateur empêche les instanciations des classes abstraites au lieu de générer une erreur lors de 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 __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
Traverser_
la source
3

Ce genre de situations 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;
}
juanjo
la source
3

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

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

Ici, vous obtiendrez un message comme:

<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 affirmation:

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

Dans ce cas, vous obtiendrez:

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

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

gbk
la source
2

Le cacao ne fournit rien appelé abstrait. Nous pouvons créer un résumé de classe qui n'est vérifié qu'au moment de l'exécution, et au moment de la compilation, ce n'est pas vérifié.

Hussain Shabbir
la source
1

Je désactive généralement la méthode init dans une classe que je souhaite abstraire:

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

Objective-C n'a aucun moyen intégré pour déclarer des classes abstraites.

Ali
la source
Mais je reçois une erreur lorsque j'appelle [super init] à partir de la classe dérivée de cette classe abstraite. Comment résoudre ça?
Sahil Doshi
@SahilDoshi Je suppose que c'est l'inconvénient d'utiliser cette méthode. Je l'utilise quand je ne veux pas autoriser une classe à être instanciée, et rien n'hérite de cette classe.
Ali
oui, je l'ai compris. mais votre solution vous aidera à créer quelque chose comme une classe singleton où nous ne voulons pas que quelqu'un appelle init. selon mes connaissances en cas de classe abstraite, une classe va en hériter. sinon quelle est l'utilisation de la classe abstraite.
Sahil Doshi
0

En modifiant un peu ce que @redfood a suggéré en appliquant le commentaire de @ dotToString, vous avez en fait la solution adoptée par IGListKit d'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 ajouter à cette classe toutes les autres méthodes qui ont du sens d'avoir une implémentation commune.
  3. Partout dans votre projet, si un enfant de AbstractClassdoit être entré ou sorti par une méthode, saisissez-le à la AbstractClass<Protocol>place.

Parce que AbstractClassn'implémente pas Protocol, la seule façon d'avoir une AbstractClass<Protocol>instance est de sous-classer. Comme AbstractClassseul ne peut être utilisé nulle part dans le projet, il devient abstrait.

Bien sûr, cela n'empêche pas les développeurs déconseillés d'ajouter de nouvelles méthodes se référant simplement à AbstractClass, ce qui finirait par autoriser une instance de la classe abstraite (plus maintenant).

Exemple réel: IGListKit a une classe de base IGListSectionControllerqui n'implémente pas le protocole IGListSectionType, cependant chaque méthode qui nécessite une instance de cette classe, demande en fait le type IGListSectionController<IGListSectionType>. Par conséquent, il n'y a aucun moyen d'utiliser un objet de type IGListSectionControllerpour quelque chose d'utile dans leur cadre.

Gobe
la source
0

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

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
la source
0

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

// 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
la source
-3

Vous ne pouvez 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 le comportement.

Cela ressemble à une classe de base abstraite pour moi.

user1709076
la source