Meilleure façon de définir des méthodes privées pour une classe dans Objective-C

355

Je viens de commencer à programmer Objective-C et, ayant une formation en Java, je me demande comment les gens qui écrivent des programmes Objective-C traitent des méthodes privées.

Je comprends qu'il peut y avoir plusieurs conventions et habitudes et je considère cette question comme un agrégateur des meilleures techniques que les gens utilisent pour traiter les méthodes privées dans Objective-C.

Veuillez inclure un argument pour votre approche lors de sa publication. Pourquoi est-il bon? Quels sont ses inconvénients (à votre connaissance) et comment les traitez-vous?


Quant à mes conclusions jusqu'à présent.

Il est possible d'utiliser des catégories [par exemple MyClass (Private)] définies dans le fichier MyClass.m pour regrouper des méthodes privées.

Cette approche comporte 2 problèmes:

  1. Xcode (et le compilateur?) Ne vérifie pas si vous définissez toutes les méthodes dans la catégorie privée dans le bloc @implementation correspondant
  2. Vous devez mettre @interface déclarant votre catégorie privée au début du fichier MyClass.m, sinon Xcode se plaint d'un message comme "self peut ne pas répondre au message" privateFoo ".

Le premier problème peut être contourné avec une catégorie vide [par exemple MyClass ()].
Le second me dérange beaucoup. J'aimerais voir des méthodes privées implémentées (et définies) vers la fin du fichier; Je ne sais pas si c'est possible.

Yurii Soldak
la source
1
Les gens pourraient trouver cette question intéressante: stackoverflow.com/questions/2158660/…
bbum
4
Pourquoi ne pas simplement laisser de côté la déclaration de la méthode privée ?
ma11hew28

Réponses:

435

Il n'y a pas, comme d'autres l'ont déjà dit, de méthode privée dans Objective-C. Cependant, à partir d'Objective-C 2.0 (c'est-à-dire Mac OS X Leopard, iPhone OS 2.0 et versions ultérieures), vous pouvez créer une catégorie avec un nom vide (c'est-à-dire @interface MyClass ()) appelé Extension de classe . Ce qui est unique dans une extension de classe, c'est que les implémentations de méthodes doivent aller de la même manière @implementation MyClassque les méthodes publiques. Je structure donc mes cours comme ceci:

Dans le fichier .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

Et dans le fichier .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

Je pense que le plus grand avantage de cette approche est qu'elle vous permet de regrouper vos implémentations de méthode par fonctionnalité, et non par la distinction (parfois arbitraire) public / privé.

Alex
la source
8
et il générera une "MYClass peut ne pas répondre à '-myPrivateMethod-", pas une exception / erreur.
Özgür
2
Cela commence en fait à apparaître dans le code passe-partout d'Apple. ++
Chris Trahey
75
avec le compilateur LLVM 4 et les versions ultérieures, vous n'avez même pas besoin de le faire. vous pouvez simplement les définir dans votre implémentation sans avoir besoin de les mettre dans une extension de classe.
Abizern
1
Si vous obtenez les avertissements mentionnés par @Comptrol, c'est parce que vous avez défini une méthode ci-dessous plutôt qu'au-dessus d'une autre méthode qui l'appelle (voir la réponse d'Andy) - et vous ignorez ces avertissements à vos risques et périls. J'ai fait cette erreur et le compilateur s'est bien débrouillé jusqu'à ce que if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...j'imbrique un appel comme celui-ci: Ensuite, fWidthCombined était toujours à 0.
Wienke
6
@Wienke Il n'est plus nécessaire de s'inquiéter de la commande. Les versions récentes de LLVM trouveront la méthode même si elle apparaît ci-dessous où elle est appelée.
Steve Waddicor
52

Il n'y a pas vraiment de "méthode privée" dans Objective-C, si le runtime peut déterminer quelle implémentation utiliser il le fera. Mais cela ne veut pas dire qu'il n'y a pas de méthodes qui ne font pas partie de l'interface documentée. Pour ces méthodes, je pense qu'une catégorie est très bien. Plutôt que de placer le @interfaceen haut du fichier .m comme votre point 2, je le mettrais dans son propre fichier .h. Une convention que je suis (et j'ai vu ailleurs, je pense que c'est une convention Apple car Xcode le prend désormais automatiquement en charge) consiste à nommer un tel fichier d'après sa classe et sa catégorie avec un + les séparant, donc @interface GLObject (PrivateMethods)on peut le trouver dans GLObject+PrivateMethods.h. La raison de fournir le fichier d'en-tête est que vous puissiez l'importer dans vos classes de tests unitaires :-).

Soit dit en passant, en ce qui concerne l'implémentation / définition des méthodes vers la fin du fichier .m, vous pouvez le faire avec une catégorie en implémentant la catégorie au bas du fichier .m:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

ou avec une extension de classe (ce que vous appelez une "catégorie vide"), définissez simplement ces méthodes en dernier. Les méthodes Objective-C peuvent être définies et utilisées dans n'importe quel ordre dans l'implémentation, donc rien ne vous empêche de mettre les méthodes "privées" à la fin du fichier.

Même avec des extensions de classe, je vais souvent créer un en-tête séparé ( GLObject+Extension.h) afin que je puisse utiliser ces méthodes si nécessaire, imitant la visibilité "ami" ou "protégé".

Depuis que cette réponse a été écrite à l'origine, le compilateur clang a commencé à faire deux passes pour les méthodes Objective-C. Cela signifie que vous pouvez éviter de déclarer complètement vos méthodes "privées" et qu'elles soient au-dessus ou en dessous du site appelant, elles seront trouvées par le compilateur.


la source
37

Bien que je ne sois pas un expert Objective-C, je ne définis personnellement que la méthode dans l'implémentation de ma classe. Certes, il doit être défini avant (ci-dessus) toutes les méthodes qui l'appellent, mais cela prend certainement le moins de travail à faire.

Andy
la source
4
Cette solution a l'avantage qu'elle évite d'ajouter une structure de programme superflue juste pour éviter un avertissement du compilateur.
Jan Hettich
1
J'ai tendance à le faire également, mais je ne suis pas non plus un expert d'Objectif-C. Pour les experts, y a-t-il une raison de ne pas procéder de cette façon (en dehors du problème de commande de méthode)?
Eric Smith
2
L'ordre des méthodes semble être un problème mineur, mais si vous le traduisez en lisibilité du code, il peut devenir un problème assez important, surtout lorsque vous travaillez en équipe.
borisdiakur
17
L'ordre des méthodes n'est plus significatif. Les versions récentes de LLVM ne se soucient pas de l'ordre dans lequel les méthodes sont implémentées. Vous pouvez donc vous adapter à la commande, sans avoir à le déclarer au préalable.
Steve Waddicor
Voir aussi cette réponse de @justin
lagweezle
19

La définition de vos méthodes privées dans le @implementationbloc est idéale dans la plupart des cas. Clang les verra dans @implementation, indépendamment de l'ordre de déclaration. Il n'est pas nécessaire de les déclarer dans une continuation de classe (aka extension de classe) ou une catégorie nommée.

Dans certains cas, vous devrez déclarer la méthode dans la continuation de classe (par exemple si vous utilisez le sélecteur entre la continuation de classe et le @implementation).

static les fonctions sont très bonnes pour les méthodes privées particulièrement sensibles ou sensibles à la vitesse.

Une convention pour nommer les préfixes peut vous aider à éviter d'écraser accidentellement les méthodes privées (je trouve le nom de classe comme un préfixe sûr).

Les catégories nommées (par exemple @interface MONObject (PrivateStuff)) ne sont pas une bonne idée en raison des collisions de noms potentielles lors du chargement. Ils ne sont vraiment utiles que pour les méthodes amis ou protégées (qui sont très rarement un bon choix). Pour vous assurer d'être averti des implémentations de catégorie incomplètes, vous devez réellement l'implémenter:

@implementation MONObject (PrivateStuff)
...HERE...
@end

Voici un petit aide-mémoire annoté:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

Une autre approche qui peut ne pas être évidente: un type C ++ peut être à la fois très rapide et fournir un degré de contrôle beaucoup plus élevé, tout en minimisant le nombre de méthodes objc exportées et chargées.

Justin
la source
1
+1 pour utiliser le nom complet de la classe comme préfixe de nom de méthode! C'est beaucoup plus sûr qu'un simple trait de soulignement ou même votre propre TLA. (Et si la méthode privée se trouve dans une bibliothèque que vous utilisez dans un autre de vos projets et que vous oubliez que vous avez déjà utilisé le nom, il y a un an ou deux ...?)
big_m
14

Vous pouvez essayer de définir une fonction statique au-dessous ou au-dessus de votre implémentation qui prend un pointeur vers votre instance. Il pourra accéder à n'importe laquelle de vos variables d'instances.

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end
2 tours
la source
1
Apple a réservé des noms avec un trait de soulignement pour ses propres utilisations.
Georg Schölly
1
Et si vous n'utilisez pas les frameworks d'Apple? Je développe fréquemment du code Objective-C sans les frameworks d'Apple, en fait je m'appuie sur Linux, Windows et Mac OS X. Je l'ai quand même supprimé car la plupart des gens qui codent en Objective-C l'utilisent probablement sur Mac OS X.
dreamlax
3
Je pense que c'est une méthode vraiment privée dans un fichier .m. Les autres méthodes de catégorie de classe ne sont en fait pas privées, car vous ne pouvez tout simplement pas mettre des méthodes privées pour @interface ... @ end block.
David.Chu.ca
Pourquoi ferais-tu ça? si vous ajoutez simplement "-" au début de la définition de la méthode, vous accéderez à "self" sans passer en paramètre.
Guy
1
@Guy: car alors la méthode est détectable par réflexion, et donc pas du tout privée.
dreamlax
3

Vous pourriez utiliser des blocs?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

Je sais que c'est une vieille question, mais c'est l'une des premières que j'ai trouvées lorsque je cherchais une réponse à cette question. Je n'ai vu cette solution discutée nulle part ailleurs, alors faites-moi savoir s'il y a quelque chose de stupide à faire cela.

FellowMD
la source
1
Ce que vous avez fait ici est de créer une variable globale de type bloc, qui n'est pas vraiment meilleure qu'une fonction (et même pas vraiment privée, car elle n'est pas déclarée static). Mais j'ai expérimenté l'attribution de blocs aux ivars privés (à partir de la méthode init) - un peu à la manière de JavaScript - qui permet également d'accéder aux ivars privés, ce qui n'est pas possible à partir des fonctions statiques. Pas encore sûr de ce que je préfère.
big_m
3

tous les objets dans Objective C sont conformes au protocole NSObject, qui conserve la méthode performSelector : . Je cherchais également auparavant un moyen de créer des méthodes "auxiliaires ou privées" que je n'avais pas besoin d'exposer au niveau public. Si vous souhaitez créer une méthode privée sans surcharge et sans avoir à la définir dans votre fichier d'en-tête, essayez ceci ...

définir votre méthode avec une signature similaire à celle du code ci-dessous ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

puis quand vous avez besoin de référencer la méthode, appelez-la simplement comme sélecteur ...

[self performSelector:@selector(myHelperMethod:)];

cette ligne de code invoquera la méthode que vous avez créée et n'aura pas d'avertissement gênant de ne pas l'avoir définie dans le fichier d'en-tête.

Zack Sheppard
la source
6
De cette façon, vous n'avez aucun moyen de passer un troisième paramètre.
Li Fumin
2

Si vous vouliez éviter le @interfaceblocage en haut, vous pouviez toujours mettre les déclarations privées dans un autre fichier, ce qui MyClassPrivate.hn'était pas idéal, mais cela n'encombrait pas l'implémentation.

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end
rebelzach
la source
2

Une autre chose que je n'ai pas vue mentionnée ici - Xcode prend en charge les fichiers .h avec "_private" dans le nom. Disons que vous avez une classe MyClass - vous avez MyClass.m et MyClass.h et maintenant vous pouvez également avoir MyClass_private.h. Xcode reconnaîtra cela et l'inclura dans la liste des "homologues" dans l'éditeur adjoint.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
Rich Schonthal
la source
1

Il n'y a aucun moyen de contourner le problème # 2. C'est juste la façon dont le compilateur C (et donc le compilateur Objective-C) fonctionne. Si vous utilisez l'éditeur XCode, la fenêtre contextuelle de la fonction devrait faciliter la navigation dans les blocs @interfaceet @implementationdans le fichier.

Barry Wark
la source
1

Il y a un avantage à l'absence de méthodes privées. Vous pouvez déplacer la logique que vous aviez l'intention de masquer dans la classe distincte et l'utiliser comme délégué. Dans ce cas, vous pouvez marquer l'objet délégué comme privé et il ne sera pas visible de l'extérieur. Déplacer la logique vers une classe séparée (peut-être plusieurs) améliore la conception de votre projet. Parce que vos classes deviennent plus simples et vos méthodes sont regroupées en classes avec des noms propres.

Sneg
la source
0

Comme d'autres l'ont dit, la définition de méthodes privées dans le @implementationbloc est OK dans la plupart des cas.

Sur le sujet de l' organisation du code - j'aime les garder ensemble pragma mark privatepour une navigation plus facile dans Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Milan
la source