web-dev-qa-db-fra.com

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

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

Je comprends qu’il peut exister plusieurs conventions et habitudes et j’envisage cette question comme un agrégateur des meilleures techniques que les gens utilisent pour traiter les méthodes privées en 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 gérer?


En ce qui concerne mes conclusions jusqu'à présent.

Il est possible d’utiliser catégories [p. Ex. MyClass (Private)] définie dans le fichier MyClass.m pour regrouper les méthodes privées.

Cette approche a 2 problèmes:

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

Le premier numéro peut être contourné avec catégorie vide [p. Ex. Ma classe ()].
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.

350
Yurii Soldak

Comme d'autres l'ont déjà dit, il n'existe pas de méthode privée dans Objective-C. Toutefois, à partir de Objective-C 2.0 (Mac OS X Leopard, iPhone OS 2.0 et ultérieur), vous pouvez créer une catégorie avec un nom vide (c'est-à-dire @interface MyClass ()) appelée Extension de classe. La particularité d'une extension de classe est que les implémentations de méthodes doivent aller dans le même @implementation MyClass que les méthodes publiques. Donc, je structure 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 les implémentations de vos méthodes par fonctionnalité et non par distinction (parfois arbitraire) public/privé.

433
Alex

Il n'y a pas vraiment de "méthode privée" dans Objective-C, si le moteur d'exécution 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 convient. Plutôt que de placer le @interface en haut du fichier .m comme votre point 2, je le mettrais dans son propre fichier .h. Une convention que je suis (et que j’ai vue ailleurs, je pense que c’est une convention Apple puisque Xcode l’a maintenant automatiquement prise en charge) consiste à nommer un tel fichier après sa classe et sa catégorie avec un + les séparant, donc @interface GLObject (PrivateMethods) peut être trouvé dans GLObject+PrivateMethods.h. La raison de la fourniture du fichier d’en-tête est que vous pouvez l’importer dans vos classes de tests unitaires :-).

En passant, en ce qui concerne la mise en œuvre/la définition de méthodes à 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 de l'implémentation. Par conséquent, rien ne vous empêche de placer 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 distinct (GLObject+Extension.h) afin de pouvoir utiliser ces méthodes si nécessaire, en imitant la visibilité "ami" ou "protégée".

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

50
user23743

Bien que je ne sois pas un expert d'Objective-C, j'ai personnellement défini la méthode dans la mise en œuvre de ma classe. Certes, il doit être défini avant (ci-dessus) toute méthode l’appelant, mais cela demande certainement le moins de travail possible.

37
Andy

Définir vos méthodes privées dans le bloc @implementation est idéal dans la plupart des cas. Clang les verra dans le @implementation, quel que soit l'ordre de déclaration. Il n'est pas nécessaire de les déclarer dans une continuation de classe (ou extension de classe) ou une catégorie nommée.

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

Les fonctions static conviennent très bien aux méthodes privées particulièrement sensibles ou critiques.

Une convention pour nommer les préfixes peut vous aider à éviter de surcharger accidentellement les méthodes privées (je trouve le nom de la classe comme préfixe safe).

Les catégories nommées (par exemple, @interface MONObject (PrivateStuff)) ne sont pas une bonne idée en raison des risques de collision de noms lors du chargement. Elles ne sont vraiment utiles que pour les méthodes d'amis ou protégées (qui sont très rarement un bon choix). Pour vous assurer d'être prévenu de l'implémentation incomplète des catégories, vous devez en réalité 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.

19
justin

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

//.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
14
dreamlax

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 pas vu cette solution discutée ailleurs, alors laissez-moi savoir s'il y a quelque chose d'idiot à faire cela.

3
FellowMD

chaque objet de l’objectif C est conforme au protocole NSObject, qui s’applique à la méthode performSelector:. J'étais également à la recherche d'un moyen de créer des méthodes "d'assistance ou privées" dont je n'avais pas besoin d'être exposées au niveau public. Si vous souhaitez créer une méthode privée sans surcharge et ne pas avoir à la définir dans votre fichier d'en-tête, essayez-le ...

définissez votre méthode avec une signature similaire au code ci-dessous ...

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

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

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

cette ligne de code appellera la méthode que vous avez créée et ne sera pas avertie de ne pas l'avoir définie dans le fichier d'en-tête.

3
Zack Sheppard

Si vous voulez éviter le bloc @interface au sommet, vous pouvez toujours mettre les déclarations privées dans un autre fichier MyClassPrivate.h pas idéal, mais il ne faut pas encombrer 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
2
rebelzach

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

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
2
Rich Schonthal

Il n'y a aucun moyen de contourner le problème n ° 2. C’est ainsi que fonctionnent le compilateur C (et donc le compilateur Objective-C). Si vous utilisez l'éditeur XCode, la fonction popup devrait faciliter la navigation dans les blocs @interface et @implementation du fichier.

1
Barry Wark

Il y a un avantage de l'absence de méthodes privées. Vous pouvez déplacer la logique que vous souhaitez masquer vers la classe séparée et l'utiliser en tant que délégué. Dans ce cas, vous pouvez marquer un objet délégué comme privé et il ne sera pas visible de l'extérieur. Le fait de 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 dans des classes avec des noms propres.

1
Sneg

Comme d'autres personnes l'ont dit, définir des méthodes privées dans le bloc @implementation est acceptable dans la plupart des cas.

À propos de organisation du code - J'aime les garder ensemble sous pragma mark private pour faciliter la navigation dans Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
0
Milan