Création d'une classe abstraite dans L'objectif-C
je suis à l'origine un programmeur Java qui travaille maintenant avec objectif-C. j'aimerais créer une classe abstraite, mais cela ne semble pas possible dans objectif-C. Est-ce possible?
si ce n'est pas le cas, à quelle distance d'une classe abstraite puis-je me rapprocher de L'objectif-C?
21 réponses
Typiquement, la classe Objective-C est abstraite par convention seulement-si l'auteur documente une classe comme abstraite, il suffit de ne pas l'utiliser sans la sous -classer. Cependant, il n'y a pas d'application de compilations 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 (i.e. à l'exécution). Vous pouvez forcer un utilisateur à au moins outrepasser certaines méthodes en soulevant 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];
tant que vous n'avez pas besoin d'ajouter une instruction de retour de la méthode.
si la classe abstraite est réellement une interface (c'est-à-dire qu'elle n'a pas d'implémentation de méthode concrète), l'utilisation d'un protocole objectif-C est l'option la plus appropriée.
Non, il n'y a aucun moyen de créer une classe abstraite en Objective-C.
vous pouvez simuler une classe abstraite - en faisant les méthodes/ sélecteurs appeler doesNotRecognizeSelector: et donc soulever une exception rendant la classe inutilisable.
par exemple:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
vous pouvez aussi le faire pour init.
j'ai juste riffé sur la réponse de @ Barry Wark ci-dessus (et mise à jour pour iOS 4.3) et en laissant ceci 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 ce
- (void) someMethod {
mustOverride(); // or methodNotImplemented(), same thing
}
Notes: Je ne suis pas sûr si faire passer une macro pour une fonction C est une bonne idée ou pas, mais je vais la garder jusqu'à ce que j'ai appris le 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 lance en réponse à doesNotRecognizeSelector
étant appelé (voir NSObject
docs).
la solution que j'ai trouvée est:
- créez un protocole pour tout ce que vous voulez dans votre classe "abstract "
- créer une classe de base (ou peut-être l'appeler abstract) qui implémente le protocole. Pour toutes les méthodes que vous voulez "abstrait" de les mettre en œuvre dans le .m fichier, mais pas le .h fichier.
- faites hériter votre classe d'enfant de la classe de base et implémentez le protocole.
This la façon dont le compilateur vous donnera un avertissement pour toute méthode dans le 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 de compilateur désiré.
De la l'Omni Group mailing list :
objectif-C n'a pas la construction de compilateur Abstrait comme Java à cette fois.
donc tout ce que vous faites est de définir la classe abstraite comme toute autre classe normale et mettre en œuvre des stubs de méthodes pour les méthodes abstraites qui sont soit vide ou Rapport de non-prise en charge du sélecteur. Exemple...
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
je fais aussi ce qui suit pour empêcher la l'initialisation de l'abstrait classe via l'initialisation 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 objets qui sont conformes au protocole et d'implémenter les méthodes. Par exemple, je peux définir un protocole D'opération, et ensuite avoir une fonction comme celle-ci:
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
où op peut être tout objet mettant en œuvre le protocole D'opération.
Si vous avez besoin de votre résumé classe de base 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 surcharger la fonction - (id)init et de la faire 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 pas vraiment d'équivalent direct à une classe de base abstraite.
ce fil est un peu vieux, et la plupart de ce que je veux partager est déjà ici.
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, donc je vais ici...
tout d'abord, et avant tout (comme d'autres l'ont déjà souligné) les classes abstraites sont quelque chose de très rare dans L'Objectif-C - nous utilisons habituellement 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-à l'exception des propriétés @dynamic
, qui IIRC ont été ajoutés dans ObjC 2.0 accompagnant l'introduction de CoreData.
mais étant donné que (après une évaluation attentive 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é à résoudre votre problème, Voici comment I le faire:
- mettre en œuvre toutes les méthodes abstraites dans la classe de base.
- faciliter la mise en œuvre
[self doesNotRecognizeSelector:_cmd];
... - ... suivi de
__builtin_unreachable();
pour réduire au silence l'avertissement que vous recevrez pour les méthodes non nulles, vous disant que"le contrôle atteint la fin de la fonction non null sans retour". - combiner les étapes 2. et 3. dans une macro, ou annoter
-[NSObject doesNotRecognizeSelector:]
en utilisant__attribute__((__noreturn__))
dans une catégorie sans mise en œuvre afin de ne pas remplacer l'original mise en œuvre 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 boilerplate autant que possible.
Ici, c'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
comme vous pouvez le voir, la macro fournit la pleine mise en œuvre des méthodes abstraites, réduisant la quantité nécessaire de boilerplate à un minimum absolu.
une option encore meilleure serait de faire pression sur Clang team pour fournir un attribut de compilateur pour ce cas, via des requêtes de fonctionnalités. (Mieux, parce que cela permettrait également des diagnostics de compilation-temps pour les scénarios où vous sous-classe, par exemple, Nsaccrementalstore.)
Pourquoi Je Choisis Cette Méthode
- C'est que le travail soit fait de manière efficace, et assez facilement.
- c'est assez facile à comprendre. (OK, ce
__builtin_unreachable()
peut surprendre les gens, mais c'est assez facile à comprendre, aussi.) - il ne peut pas être dépouillé dans les compilations release sans générer d'autres avertissements de compilateurs, ou des erreurs - contrairement à une approche qui est basée sur l'une des macros d'assertion.
ce dernier point nécessite quelques explications, je suppose:
Certains (la plupart?) les gens bande assertions dans les versions release. (Je pas d'accord avec cette habitude, mais c'est une autre histoire...) ne pas mettre en œuvre une méthode requise - cependant - est mauvais , terrible , faux , et essentiellement la fin de l'univers pour votre programme. Votre programme ne peut pas fonctionner correctement à cet égard, parce qu'il n'est pas défini, et indéfini comportement est la pire chose jamais. Par conséquent, être en mesure d'enlever ces diagnostics sans générer de nouveaux diagnostic serait totalement inacceptable.
il est assez mauvais que vous ne puissiez pas obtenir des diagnostics de compilation appropriés pour de telles erreurs de programmeur, et devez recourir à la découverte à l'exécution pour ceux-ci, mais si vous pouvez plâtrer au-dessus dans les constructions de libération, pourquoi essayer d'avoir une classe abstraite en premier lieu?
utilisant @property
et @dynamic
pourrait aussi fonctionner. Si vous déclarez une propriété dynamique et ne donnez pas d'implémentation de méthode correspondante, tout sera compilé sans avertissement, et vous obtiendrez une erreur unrecognized selector
à 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 Dactylographie.
Dans Xcode (à l'aide de clang, etc) j'aime utiliser __attribute__((unavailable(...)))
pour baliser les classes abstraites de sorte que vous obtenez un message d'erreur/d'avertissement si vous essayez de l'utiliser.
il offre une certaine protection contre l'utilisation accidentelle de la méthode.
exemple
Dans la classe de base @interface
tag "abstrait" des méthodes:
- (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 ce que vous obtiendrez 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 ne fais que résumer et simplifier 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 de mettre en œuvre les méthodes du protocole.
@protocol ProtocolName
// list of methods and properties
@end
Option 2: Modèle De Méthode
si vous voulez créer une classe abstraite avec une implémentation partielle comme" template Method Pattern " alors c'est la solution. Objective-C - Modèle de méthodes de modèle?
une autre alternative
il suffit de vérifier la classe dans la classe abstraite et D'affirmer ou D'Exception, peu importe 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 remplacer init
(plus d'une suggestion connexe)
j'ai voulu avoir un moyen de laisser le programmeur savoir "ne pas appeler de l'enfant" et de passer complètement (dans mon cas offrent encore une certaine fonctionnalité par défaut au nom du parent quand Non é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" override "dans la déclaration et saura qu'ils ne devraient pas appeler [super ..]
.
accordé, il est laid avoir à définir des types de retour individuels pour cela, mais il sert d'une assez bonne indication visuelle et vous pouvez facilement ne pas utiliser la partie "override_" dans une définition de sous-classe.
bien sûr, une classe peut encore avoir une implémentation par défaut lorsqu'une extension est facultatif. Mais comme le disent les autres réponses, implémentez une exception d'exécution quand c'est approprié, comme pour les classes abstraites (virtuelles).
ce serait bien d'avoir intégré des astuces de compilateur comme celle-ci, même conseils pour quand il est préférable de pré/post appeler l'implémentation du super, au lieu d'avoir à creuser à travers des commentaires/documentation ou... supposer.
si vous êtes habitué à ce que le compilateur détecte des violations d'instanciation abstraite dans d'autres langues, alors le comportement objectif-c est décevant.
en tant que langage contraignant tardif, il est clair que L'Objectif-C ne peut pas prendre de décisions statiques sur le fait qu'une classe est réellement abstraite ou non (vous pourriez ajouter des fonctions à l'exécution...), mais dans les cas d'utilisation typiques, cela semble être une lacune. Je préférerais le compilateur flat-out empêchait les instanciations de classes abstraites au lieu de jeter 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 cacher les initialisateurs:
//
// 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
probablement ce genre de situations ne devrait 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:[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 l'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'un des plus simples.
cacao ne fournit rien appelé abstrait. Nous pouvons créer un abstract de classe qui est vérifié seulement à l'exécution, et au moment de la compilation ce n'est pas vérifié.
j'ai l'habitude de juste désactiver la méthode init dans une classe que je veux 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.
objectif-c n'a pas de manière intégrée pour déclarer des classes abstraites.
changer un peu ce que @redfood a suggéré en appliquant le commentaire de @dotToString, vous avez en fait la solution adoptée par Instagram IGListKit .
- créer un protocole pour toutes les méthodes qui n'ont aucun sens d'être définies dans la classe de base (abstraite), c'est-à-dire qu'elles ont besoin d'implémentations spécifiques dans les enfants.
- créer une classe de base (abstraite) que ne mettre en œuvre ce protocole. Vous pouvez ajouter à cette classe toutes les autres méthodes sensées pour avoir une implémentation commune.
- partout dans votre projet, si un enfant de
AbstractClass
doit être entrée ou sortie par une méthode quelconque, tapez-le commeAbstractClass<Protocol>
à la place.
parce que AbstractClass
ne met pas en œuvre Protocol
, la seule façon d'avoir une instance AbstractClass<Protocol>
est par subdivision. Comme AbstractClass
seul ne peut pas être utilisé n'importe où dans le projet, il devient abstrait.
bien sûr, cela n'empêche pas les développeurs non avertis d'ajouter de nouvelles méthodes se référant simplement à AbstractClass
, qui finiraient par autoriser une instance de la classe abstraite (plus maintenant).
exemple du monde réel: IGListKit a une classe de base IGListSectionController
qui 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 IGListSectionController
pour quelque chose d'utile dans leur cadre.
en fait, Objective-C n'a pas de classes abstraites, mais vous pouvez utiliser protocoles pour obtenir le même effet. Voici l'échantillon:
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
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
ne pouvez-vous pas 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.
puis chaque fois que vous mettez en œuvre votre délégué (I. e abstract class) vous êtes averti par le compilateur des fonctions optionnelles et obligatoires pour lesquelles vous devez définir behavior.
cela ressemble à une classe de base abstraite pour moi.