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?

31
demandé sur Josh Caswell 2012-09-28 08:34:22

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.

31
répondu Carl Veazey 2015-07-17 06:59:53

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.

13
répondu d4n3 2014-02-06 15:53:20

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
7
répondu Alex Smith 2015-02-01 11:57:58

, 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
1
répondu 8vius 2012-09-28 04:43:53

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

1
répondu smileBot 2015-10-29 01:25:55

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
1
répondu Chuck Krutsinger 2016-09-02 00:02:44

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.

0
répondu Scott Berrevoets 2012-09-28 04:38:17