web-dev-qa-db-fra.com

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

Selon certaines discussions officielles, une classe dans Objective-C ne devrait exposer que les méthodes et 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 dans le fichier .m:

@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;

- (void) privateMethod;

@end

et je ne pense pas qu'il existe un type protected pour les éléments privés mais accessibles à partir de sous-classes. Je me demande s'il existe un moyen d'y parvenir, mis à part la déclaration publique des propriétés/méthodes privées.

31
hzxu

Une façon de résoudre ce problème consiste à re-déclarer la propriété dans l'extension de classe de votre sous-classe, puis à ajouter une instruction @dynamic afin que le compilateur ne crée pas d'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 de manière privée. Mais comme il est assez pratique et utile dans certaines situations, je dirais d'évaluer au cas par cas les dangers de cette duplication par rapport à la divulgation 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, nommé explicitement "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.

34
Carl Veazey

Cela est possible en utilisant une extension de classe (pas une 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 qui pourront synthétiser les ivars de support (XCode> 4.4. La synthèse automatique des ivars fonctionne également ici).

Dans la classe d’extension, vous pouvez remplacer/affiner les propriétés (changer readonly en readwrite, etc.) et ajouter des propriétés et des méthodes qui seront "visibles" pour les fichiers d’implémentation (mais notez que les propriétés et les méthodes ne sont pas vraiment privées et peuvent encore être appelé par sélecteur).

D'autres ont proposé d'utiliser un fichier d'en-tête séparé, MyClass_protected.h, mais cela peut aussi être fait dans le fichier d'en-tête principal en utilisant #ifdef comme ceci:

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 le fichier .h à l’endroit où vous l’importez . Si vous avez un #ifdef, le code qu’il contient n’est inclus que si le #define portant ce nom est défini.

Dans votre fichier .h, vous ne définissez pas la définition. Les classes qui importent ce fichier .h ne verront pas l'extension de classe protégée . Dans le fichier .m de la classe et de la sous-classe de base, vous utilisez #define avant d'utiliser #import afin que le compilateur comprendra l'extension de classe protégée.

14
d4n3

Bien que les autres réponses soient correctes, j'aimerais ajouter ...

Privé, protégé et public sont disponibles par exemple variables en tant que tels:

@interface MyClass : NSObject {
@private
  int varA;

@protected
  int varB;

@public
  int varC;
}

@end
8
Alex Smith

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éthodes, vous pouvez créer une catégorie et y avoir toutes vos méthodes et attributs protégés, mais au final tout restera public.

#import "MyClass.h"

@interface MyClass (Protected)

- (void) protectedMethods;

@end
1
8vius

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 ces réponses. Voici comment j'ai exposé avec succès des méthodes privées à la sous-classe à l'aide d'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
1
Chuck Krutsinger

Créez simplement un fichier .h avec votre extension de classe. Importez ceci dans vos fichiers .m. 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

1
smileBot

C'est parce qu'il n'y a même pas de réelle distinction entre privé et public. Tandis que le compilateur peut vous avertir d'une interface manquant d'une certaine méthode ou variable d'instance, votre programme fonctionnera toujours.

0
Scott Berrevoets