35 votes

Exposer une méthode ou une propriété Objective-C privée aux sous-classes

Selon certaines discussions officielles, une classe en 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 devraient être conservées dans une extension de classe dans le fichier .m :

@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;

- (void) privateMethod;

@end

et je ne pense pas qu'il y ait un type protected pour les éléments qui sont privés mais accessibles depuis les sous-classes. Je me demande, y a-t-il un moyen d'atteindre cela, en dehors de déclarer les propriétés/méthodes privées publiquement ?

35voto

Carl Veazey Points 12122

Une façon de résoudre cela est de redéclarer 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 de remplacement 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 cela duplique une déclaration visible en privé. Mais c'est assez pratique et utile dans certaines situations donc je dirais d'évaluer au cas par cas les dangers liés à cette duplication par rapport à l'exposition de la propriété dans l'interface publique.

Une alternative - utilisée par Apple dans UIGestureRecognizer - est de déclarer la propriété dans un fichier d'en-tête de catégorie séparé explicitement nommé "private" ou "protected", par exemple "SomeClass+Protected.h". De cette façon, les autres programmeurs sauront qu'ils ne devraient pas importer le fichier. Mais, si vous ne contrôlez pas le code à partir duquel vous héritez, ce n'est pas une option.

16voto

d4n3 Points 563

Cela 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 de manière similaire à 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, qui pourront synthétiser les ivars de sauvegarde (la synthèse automatique des ivars fonctionne également ici avec Xcode > 4.4).

Dans la classe d'extension, vous pouvez remplacer/ajuster les propriétés (passer de readonly à readwrite, etc.), et ajouter des propriétés et des méthodes qui seront "visibles" dans les 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 le sélecteur).

D'autres ont suggéré 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 ceci :

Exemple :

BaseClass.h

@interface BaseClass : NSObject

// foo est en lecture seule pour les consommateurs de la classe
@property (nonatomic, readonly) NSString *foo;

@end

#ifdef BaseClass_protected

// ceci est l'extension de classe, où vous définissez
// les propriétés et méthodes "protégées" de la classe

@interface BaseClass ()

// foo est maintenant en lecture-écriture
@property (nonatomic, readwrite) NSString *foo;

// bar est visible dans l'implémentation des sous-classes
@property (nonatomic, readwrite) int bar;

-(void)baz;

@end

#endif

BaseClass.m

// cela importera BaseClass.h
// avec BaseClass_protected défini,
// donc cela inclura également l'extension de classe protégée

#define BaseClass_protected
#import "BaseClass.h"

@implementation BaseClass

-(void)baz {
    self.foo = @"test";
    self.bar = 123;
}

@end

ChildClass.h

// cela importera BaseClass.h sans l'extension de classe

#import "BaseClass.h"

@interface ChildClass : BaseClass

-(void)test;

@end

ChildClass.m

// cela importera implicitement BaseClass.h depuis ChildClass.h,
// avec BaseClass_protected défini,
// donc cela inclura également l'extension de classe protégée

#define BaseClass_protected 
#import "ChildClass.h"

@implementation ChildClass

-(void)test {
    self.foo = @"test";
    self.bar = 123;

    [self baz];
}

@end

Lorsque vous appelez #import, cela revient essentiellement à copier-coller le fichier .h à l'endroit où vous l'importez. Si vous avez un #ifdef, cela inclura uniquement le code à l'intérieur si le #define avec ce nom est défini.

Dans votre fichier .h, vous ne définissez pas le #define donc les classes important ce .h ne verront pas l'extension de classe protégée. Dans le fichier .m de la classe de base et de la sous-classe, vous utilisez #define avant d'utiliser #import pour que le compilateur inclue l'extension de classe protégée.

8voto

Alex Smith Points 468

Alors que les autres réponses sont correctes, je voudrais ajouter...

Private, protected et public sont disponibles pour les variables d'instance telles que:

@interface MyClass : NSObject {
@private
  int varA;

@protected
  int varB;

@public
  int varC;
}

@end

1voto

8vius Points 3076

Votre seule option est de le déclarer comme public dans le fichier d'en-tête. Si vous voulez maintenir au moins une certaine séparation des méthodes, vous pouvez créer une catégorie et y mettre toutes vos méthodes et attributs protégés, mais à la fin tout sera quand même public.

#import "MyClass.h"

@interface MyClass (Protected)

- (void) protectedMethods;

@end

1voto

cocoanut Points 2396

Il suffit de créer un fichier .h avec l'extension de votre classe. Importez-le dans vos fichiers .m. En passant, c'est une excellente façon 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
// implémentez privateMethod ici et tous les setters ou getters avec des valeurs calculées
@end

Voici l'idée de base : https://gist.github.com/philosopherdog/6461536b99ef73a5c32a

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X