Exposer une méthode ou une propriété Objective-C privée à des sous-classes
Selon certains discours officiels, une classe dans Objective-C ne devrait exposer que des méthodes et des propriétés publiques dans son en-tête:
@interface MyClass : NSObject
@property (nonatomic, strong) MyPublicObject *publicObject;
- (void)publicMethod;
@end
Et les méthodes/propriétés privées doivent être conservées dans l'extension de classe .m fichier:
@interface MyClass()
@property (nonatomic, strong) MyPrivateObject *privateObject;
- (void) privateMethod;
@end
Et je ne pense pas qu'il y ait un type protected
pour les choses qui sont privées mais accessibles à partir de sous-classes. Je me demande, y a-t-il de toute façon pour y parvenir, en dehors de déclarer publiquement des propriétés/méthodes privées?
7 réponses
Une façon de résoudre ce problème est de déclarer à nouveau la propriété dans l'extension de classe de votre sous-classe, puis d'ajouter une instruction @dynamic
afin que le compilateur ne crée pas une implémentation prioritaire de cette propriété. Donc quelque chose comme:
@interface SuperClass ()
@property (nonatomic, strong) id someProperty;
@end
....
@interface SubClass ()
@property (nonatomic, strong) id someProperty;
@end
@implementation SubClass
@dynamic someProperty;
@end
Ce n'est évidemment pas idéal car il duplique une déclaration visible en privé. Mais c'est très pratique et utile dans certaines situations, donc je dirais d'évaluer au cas par cas les dangers impliqués dans cette duplication par rapport à l'exposition de la propriété dans l'interface publique.
Une alternative - utilisée par Apple dans UIGestureRecognizer - consiste à déclarer la propriété dans un fichier d'en-tête de catégorie distinct explicitement nommé "privé" ou "protégé", par exemple "SomeClass+Protected".h". De cette façon, les autres programmeurs sauront qu'ils ne doivent pas importer le fichier. Mais, si vous ne contrôlez pas le code dont vous héritez, ce n'est pas une option.
Ceci est possible en utilisant une extension de classe (pas de catégorie) que vous incluez dans les fichiers d'implémentation de la classe de base et des sous-classes.
Une extension de classe est définie comme une catégorie, mais sans le nom de la catégorie:
@interface MyClass ()
Dans une extension de classe, vous pouvez déclarer des propriétés, ce qui permettra de synthétiser les Ivars de support (Xcode > 4.4 la synthèse automatique des ivars fonctionne également ici).
Dans la classe extension, vous pouvez remplacer/affiner propriétés (changer readonly en readwrite etc.), et ajouter des propriétés et des méthodes qui seront "visibles" aux fichiers d'implémentation (mais notez que les propriétés et les méthodes ne sont pas vraiment privées et peuvent toujours être appelées par selector).
D'autres ont proposé d'utiliser un fichier d'en-tête séparé MyClass_protected.h pour cela, mais cela peut également être fait dans le fichier d'en-tête principal en utilisant #ifdef
comme ce:
Exemple:
BaseClass.h
@interface BaseClass : NSObject
// foo is readonly for consumers of the class
@property (nonatomic, readonly) NSString *foo;
@end
#ifdef BaseClass_protected
// this is the class extension, where you define
// the "protected" properties and methods of the class
@interface BaseClass ()
// foo is now readwrite
@property (nonatomic, readwrite) NSString *foo;
// bar is visible to implementation of subclasses
@property (nonatomic, readwrite) int bar;
-(void)baz;
@end
#endif
BaseClass.m
// this will import BaseClass.h
// with BaseClass_protected defined,
// so it will also get the protected class extension
#define BaseClass_protected
#import "BaseClass.h"
@implementation BaseClass
-(void)baz {
self.foo = @"test";
self.bar = 123;
}
@end
ChildClass.h
// this will import BaseClass.h without the class extension
#import "BaseClass.h"
@interface ChildClass : BaseClass
-(void)test;
@end
ChildClass.m
// this will implicitly import BaseClass.h from ChildClass.h,
// with BaseClass_protected defined,
// so it will also get the protected class extension
#define BaseClass_protected
#import "ChildClass.h"
@implementation ChildClass
-(void)test {
self.foo = @"test";
self.bar = 123;
[self baz];
}
@end
Lorsque vous appelez #import
, il copie-colle le .h fichier à l'endroit où vous l'importez.
Si vous avez un #ifdef
, il n'inclura le code que si le #define
avec ce nom est défini.
Dans votre .h fichier, vous ne définissez pas le define donc toutes les classes qui importent ceci .h l'habitude voir l'extension de classe protégée.
Dans la classe de base et la sous-classe .m fichier, vous utilisez #define
avant d'utiliser #import
afin que le compilateur inclue l'extension de classe protégée.
Alors que les autres réponses sont correctes, je voudrais ajouter...
Private, protected et public sont disponible par exemple variables en tant que tel:
@interface MyClass : NSObject {
@private
int varA;
@protected
int varB;
@public
int varC;
}
@end
, Votre seul choix est de le déclarer comme public dans le fichier d'en-tête. Si vous voulez au moins garder une séparation de méthode, vous pouvez créer une catégorie et y avoir toutes vos méthodes et attributs protégés, mais à la fin tout sera toujours public.
#import "MyClass.h"
@interface MyClass (Protected)
- (void) protectedMethods;
@end
Créez simplement un .h fichier avec votre extension de classe. Importer dans votre .m fichiers. Incidemment, c'est un excellent moyen de tester les membres privés sans casser l'encapsulation (Je ne dis pas que vous devriez tester les méthodes privées :) ).
// MyClassProtectedMembers.h
@interface MyClass()
@property (nonatomic, strong) MyPrivateObject *privateObject;
- (void) privateMethod;
@end
/////////////////
#import "MyClassProtectedMembers.h"
@implementation MyClass
// implement privateMethod here and any setters or getters with computed values
@end
Voici un résumé de l'idée: https://gist.github.com/philosopherdog/6461536b99ef73a5c32a
Je vois de bonnes réponses pour rendre les propriétés visibles, mais je ne vois pas exposer les méthodes abordées très clairement dans l'une de ces réponses. Voici comment j'ai exposé avec succès des méthodes privées à la sous-classe en utilisant une catégorie:
SomeSuperClass.m:
@implementation SomeSuperClass
-(void)somePrivateMethod:(NSString*)someArgument {
...
}
SomeChildClass.h
@interface SomeChildClass : SomeSuperClass
SomeChildClass.m
@interface SomeSuperClass (exposePrivateMethod)
-(void)somePrivateMethod:(NSString*)someArgument;
@end
@implementation SomeChildClass
-(void)doSomething {
[super somePrivateMethod:@"argument"];
}
@end
C'est parce qu'il n'y a même pas de véritable distinction entre privé et public. Bien que le compilateur puisse vous avertir qu'une interface manque une certaine méthode ou variable d'instance, votre programme fonctionnera toujours.