77 votes

Où mettre les iVars dans l'Objective-C "moderne" ?

Le livre "iOS6 by Tutorials" de Ray Wenderlich contient un chapitre très intéressant sur l'écriture de code Objective-C plus "moderne". Dans une section, le livre décrit comment déplacer les iVars de l'en-tête de la classe vers le fichier d'implémentation. Comme toutes les iVars devraient être privées, cela semble être la bonne chose à faire.

Mais jusqu'à présent, j'ai trouvé 3 façons de le faire. Chacun s'y prend différemment.

1.) Placez iVars sous @implementantion à l'intérieur d'un bloc d'accolades (c'est ainsi que cela est fait dans le livre).

2.) Mettre iVars sous @implementantion sans bloc d'accolades

3.) Mettre les iVars à l'intérieur de l'interface privée au-dessus de l'@implementation (une extension de classe).

Toutes ces solutions semblent fonctionner correctement et jusqu'à présent je n'ai pas remarqué de différence dans le comportement de mon application. Je suppose qu'il n'y a pas de "bonne" façon de faire mais je dois écrire des tutoriels et je veux choisir une seule façon de faire pour mon code.

De quel côté dois-je aller ?

Edit : Je ne parle ici que des iVars. Pas des propriétés. Seulement des variables supplémentaires dont l'objet n'a besoin que pour lui-même et qui ne doivent pas être exposées à l'extérieur.

Échantillons de code

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

155voto

rob mayoff Points 124153

La possibilité de placer des variables d'instance dans le @implementation ou dans une extension de classe, est une fonctionnalité du "runtime Objective-C moderne", qui est utilisé par toutes les versions d'iOS et par les programmes Mac OS X 64 bits.

Si vous voulez écrire des applications Mac OS X 32 bits, vous devez placer vos variables d'instance dans le fichier @interface déclaration. Il est probable que vous n'ayez pas besoin de prendre en charge une version 32 bits de votre application. OS X prend en charge les applications 64 bits depuis la version 10.5 (Leopard), qui est sortie il y a plus de cinq ans.

Supposons donc que vous n'écriviez que des applications qui utiliseront le runtime moderne. Où devez-vous placer vos ivars ?

Option 0 : Dans le @interface (Ne le faites pas)

D'abord, voyons pourquoi nous Ne le fais pas. vous voulez mettre des variables d'instance dans un @interface déclaration.

  1. Placer des variables d'instance dans un @interface expose les détails de l'implémentation aux utilisateurs de la classe. Cela peut conduire ces utilisateurs (même vous-même lorsque vous utilisez vos propres classes !) à se fier à des détails d'implémentation qu'ils ne devraient pas. (Ceci est indépendant du fait que nous déclarions ou non les ivars @private .)

  2. Placer des variables d'instance dans un @interface rend la compilation plus longue, car chaque fois que nous ajoutons, modifions ou supprimons une déclaration ivar, nous devons recompiler toutes les déclarations ivar. .m qui importe l'interface.

Donc, nous ne voulons pas mettre les variables d'instance dans la section @interface . Où devons-nous les mettre ?

Option 2 : Dans le @implementation sans appareil dentaire (Ne le faites pas)

Ensuite, discutons de votre option 2, "Mettre les iVars sous @implementantion sans bloc d'accolades". Cette option fait pas déclarer les variables d'instance ! Vous parlez de cela :

@implementation Person

int age;
NSString *name;

...

Ce code définit deux variables globales. Il ne déclare aucune variable d'instance.

Il n'y a pas de problème à définir des variables globales dans vos .m même dans votre @implementation si vous avez besoin de variables globales - par exemple, parce que vous voulez que toutes vos instances partagent un certain état, comme un cache. Mais vous ne pouvez pas utiliser cette option pour déclarer des ivars, car elle ne les déclare pas. (De plus, les variables globales privées à votre implémentation devraient généralement être déclarées comme suit static pour éviter de polluer l'espace de noms global et risquer des erreurs au moment de la liaison).

Il vous reste donc les options 1 et 3.

Option 1 : Dans le @implementation avec un appareil dentaire (Do It)

En général, il est préférable d'utiliser l'option 1 : les placer dans votre dossier principal. @implementation entre accolades, comme ceci :

@implementation Person {
    int age;
    NSString *name;
}

Nous les plaçons ici parce que leur existence reste privée, ce qui évite les problèmes que j'ai décrits précédemment, et parce qu'il n'y a généralement aucune raison de les placer dans une extension de classe.

Alors quand voulons-nous utiliser votre option 3, les mettre dans une extension de classe ?

Option 3 : Dans une extension de classe (à ne faire qu'en cas de nécessité)

Il n'y a presque jamais de raison de les placer dans une extension de classe, dans le même fichier que le fichier de la classe. @implementation . Nous pourrions tout aussi bien les mettre dans le @implementation dans ce cas.

Mais de temps en temps, nous pouvons écrire une classe qui est assez grande pour que nous voulions diviser son code source en plusieurs fichiers. Nous pouvons le faire en utilisant des catégories. Par exemple, si nous implémentons UICollectionView (une classe assez grande), nous pourrions décider de mettre le code qui gère les files d'attente des vues réutilisables (cellules et vues supplémentaires) dans un fichier source séparé. Nous pourrions le faire en séparant ces messages dans une catégorie :

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

OK, maintenant nous pouvons implémenter l'élément principal UICollectionView méthodes en UICollectionView.m et nous pouvons implémenter les méthodes qui gèrent les vues réutilisables en UICollectionView+ReusableViews.m ce qui rend notre code source un peu plus facile à gérer.

Mais notre code de gestion des vues réutilisables a besoin de quelques variables d'instance. Ces variables doivent être exposées à la classe principale @implementation en UICollectionView.m de sorte que le compilateur les émettra dans le fichier .o fichier. Et nous avons également besoin d'exposer ces variables d'instance au code en UICollectionView+ReusableViews.m pour que ces méthodes puissent utiliser les ivars.

C'est là que nous avons besoin d'une extension de classe. Nous pouvons mettre les ivars reusable-view-management dans une extension de classe dans un fichier d'en-tête privé :

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

Nous n'allons pas envoyer ce fichier d'en-tête aux utilisateurs de notre bibliothèque. Nous l'importerons simplement dans UICollectionView.m et en UICollectionView+ReusableViews.m de sorte que tout ce qui besoins pour voir ces ivars peuvent les voir. On a aussi ajouté une méthode pour que le programme principal init à appeler pour initialiser le code de gestion des vues réutilisables. Nous appellerons cette méthode à partir de -[UICollectionView initWithFrame:collectionViewLayout:] en UICollectionView.m et nous l'implémenterons dans UICollectionView+ReusableViews.m .

5voto

Darren Points 13973

L'option 2 est complètement fausse. Ce sont des variables globales, pas des variables d'instance.

Les options 1 et 3 sont essentiellement identiques. Cela ne fait absolument aucune différence.

Le choix est de savoir s'il faut placer les variables d'instance dans le fichier d'en-tête ou dans le fichier d'implémentation. L'avantage d'utiliser le fichier d'en-tête est que vous disposez d'un raccourci clavier rapide et facile (Command + Control + Up dans Xcode) pour afficher et modifier vos variables d'instance et la déclaration d'interface.

L'inconvénient est que vous exposez les détails privés de votre classe dans un en-tête public. Ce n'est pas souhaitable dans certains cas, notamment si vous écrivez du code destiné à être utilisé par d'autres. Un autre problème potentiel est que si vous utilisez Objective-C++, il est préférable d'éviter de mettre des types de données C++ dans votre fichier d'en-tête.

Les variables d'instance d'implémentation sont une excellente option pour certaines situations, mais pour la plupart de mon code, je place toujours les variables d'instance dans l'en-tête simplement parce que c'est plus pratique pour moi en tant que codeur travaillant dans Xcode. Mon conseil est de faire ce qui vous semble le plus pratique pour vous.

4voto

NSBum Points 6570

Il s'agit principalement de la visibilité de l'ivar pour les sous-classes. Les sous-classes ne seront pas en mesure d'accéder aux variables d'instance définies dans l'ivar. @implementation bloc.

Pour le code réutilisable que je prévois de distribuer (par exemple, le code d'une bibliothèque ou d'un cadre) et pour lequel je préfère ne pas exposer les variables d'instance à l'inspection publique, je suis enclin à placer les ivars dans le bloc d'implémentation (votre option 1).

3voto

JoePasq Points 3364

Vous devez placer les variables d'instance dans une interface privée au-dessus de l'implémentation. Option 3.

La documentation à lire à ce sujet est le Programmation en Objective-C guide.

Dans la documentation :

Vous pouvez définir des variables d'instance sans propriétés

La meilleure pratique consiste à utiliser une propriété sur un objet chaque fois que vous devez garder la trace d'une valeur ou d'un autre objet.

Si vous devez définir vos propres variables d'instance sans déclarer de propriété, vous pouvez les ajouter entre accolades en haut de l'interface ou de l'implémentation de la classe, comme ceci :

1voto

jshier Points 2454

Les ivars publics devraient vraiment être des propriétés déclarées dans l'@interface (probablement ce à quoi vous pensez en 1). Les ivars privés, si vous utilisez la dernière version de Xcode et le runtime moderne (OS X ou iOS 64 bits), peuvent être déclarés dans l'@implementation (2), plutôt que dans une extension de classe, ce qui est probablement ce à quoi vous pensez en 3.

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