Où placer iVars dans Objective-C «moderne»?

Le livre “iOS6 de Tutorials” de Ray Wenderlich contient un chapitre très intéressant sur l’écriture de code Objective-C plus “moderne”. Dans une section, le manuel explique comment déplacer iVars de l’en-tête de la classe vers le fichier d’implémentation. Puisque tous les iVars devraient être privés, cela semble être la bonne chose à faire.

Mais jusqu’à présent, j’ai trouvé 3 façons de le faire. Tout le monde le fait différemment.

1.) Placez iVars sous @implementantion dans un bloc d’accolades (c’est comme cela que cela se passe dans le livre).

2.) Placez iVars sous @implementantion sans bloc d’accolades

3.) Mettez iVars dans l’interface privée au dessus de @implementantion (une extension de classe)

Toutes ces solutions semblent fonctionner correctement et jusqu’à présent, je n’ai remarqué aucune différence dans le comportement de mon application. Je suppose qu’il n’y a pas de “bonne” façon de le faire, mais je dois écrire des tutoriels et je veux choisir un seul moyen pour mon code.

Dans quelle direction dois-je aller?

Edit: Je ne parle que d’iVars ici. Pas de propriétés. Seules les variables supplémentaires dont l’object n’a besoin que pour lui-même et qui ne doivent pas être exposées à l’extérieur.

Exemples de code

1)

#import "Person.h" @implementation Person { int age; NSSsortingng *name; } - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

2)

 #import "Person.h" @implementation Person int age; NSSsortingng *name; - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

3)

 #import "Person.h" @interface Person() { int age; NSSsortingng *name; } @end @implementation Person - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

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

Si vous souhaitez écrire des applications Mac OS X 32 bits, vous devez placer vos variables d’instance dans la déclaration @interface . Les chances sont que vous n’avez 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), lancée il y a plus de cinq ans.

Supposons donc que vous écrivez uniquement des applications qui utiliseront le runtime moderne. Où devriez-vous mettre vos ivars?

Option 0: dans l’ @interface (Don’t Do It)

Voyons d’abord pourquoi nous ne voulons pas placer de variables d’instance dans une déclaration @interface .

  1. Mettre des variables d’instance dans une @interface expose les détails de l’implémentation aux utilisateurs de la classe. Cela peut amener ces utilisateurs (même vous-même lorsque vous utilisez vos propres classes!) À vous fier aux détails de leur implémentation. (Ceci est indépendant du fait que nous déclarions les ivars @private .)

  2. Placer des variables d’instance dans une @interface rend la compilation plus longue, car chaque fois que nous ajoutons, modifions ou supprimons une déclaration ivar, nous devons recomstackr chaque fichier .m qui importe l’interface.

Nous ne voulons donc pas placer de variables d’instance dans @interface . Où devrions-nous les mettre?

Option 2: Dans la mise en œuvre sans accolades (Don’t Do It)

Ensuite, discutons de votre option 2, «Mettez iVars sous @implementantion sans bloc d’accolades». Cela ne déclare pas les variables d’instance! Vous parlez de cela:

 @implementation Person int age; NSSsortingng *name; ... 

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

Il est bon de définir des variables globales dans votre fichier .m , même dans votre application, si vous avez besoin de variables globales – par exemple, parce que vous souhaitez que toutes vos instances partagent un état, comme un cache. Mais vous ne pouvez pas utiliser cette option pour déclarer des ivars, car elle ne déclare pas les ivars. (En outre, les variables globales privées de votre implémentation doivent généralement être déclarées static pour éviter de polluer l’espace de noms global et de risquer des erreurs de liaison.)

Cela laisse vos options 1 et 3.

Option 1: Dans la mise en œuvre avec des accolades (Do It)

Habituellement, nous voulons utiliser l’option 1: placez-les dans votre bloc @implementation principal, entre accolades, comme ceci:

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

Nous les mettons ici parce que leur existence est privée, empêchant les problèmes que j’ai décrits plus tôt, et parce qu’il n’y a généralement aucune raison de les inclure dans une extension de classe.

Alors, quand voulons-nous utiliser votre option 3, en les plaçant dans une extension de classe?

Option 3: Dans une extension de classe (Do It Only When Necessary)

Il n’y a presque jamais de raison de les placer dans une extension de classe dans le même fichier que la mise en œuvre de la classe. Nous pourrions tout aussi bien les mettre dans la mise en œuvre dans ce cas.

Mais de temps en temps, nous pourrions écrire une classe suffisamment grande pour diviser son code source en plusieurs fichiers. Nous pouvons le faire en utilisant des catégories. Par exemple, si nous implémentions UICollectionView (une classe plutôt grande), nous pourrions décider de placer le code qui gère les files d’attente des vues réutilisables (cellules et vues supplémentaires) dans un fichier source distinct. 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:(NSSsortingng *)identifier; - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSSsortingng *)identifier; - (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSSsortingng *)elementKind withReuseIdentifier:(NSSsortingng *)identifier; - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSSsortingng *)kind withReuseIdentifier:(NSSsortingng *)identifier; - (id)dequeueReusableCellWithReuseIdentifier:(NSSsortingng *)identifier forIndexPath:(NSIndexPath*)indexPath; - (id)dequeueReusableSupplementaryViewOfKind:(NSSsortingng*)elementKind withReuseIdentifier:(NSSsortingng *)identifier forIndexPath:(NSIndexPath*)indexPath; @end 

OK, nous pouvons maintenant implémenter les principales méthodes UICollectionView dans UICollectionView.m et nous pouvons implémenter les méthodes qui gèrent les vues réutilisables dans UICollectionView+ReusableViews.m , ce qui rend notre code source un peu plus gérable.

Mais notre code de gestion de vue réutilisable nécessite des variables d’instance. Ces variables doivent être exposées à la classe principale @implementation dans UICollectionView.m , de sorte que le compilateur les émettra dans le fichier .o . Et nous devons également exposer ces variables d’instance au code dans UICollectionView+ReusableViews.m , afin que ces méthodes puissent utiliser les ivars.

C’est là que nous avons besoin d’une extension de classe. Nous pouvons placer les ivars de gestion des vues réutilisables 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’enverrons pas ce fichier d’en-tête aux utilisateurs de notre bibliothèque. Nous allons simplement l’importer dans UICollectionView.m et dans UICollectionView+ReusableViews.m , de sorte que tout ce qui doit voir ces ivars puisse les voir. Nous avons également ajouté une méthode que nous voulons que la méthode d’initialisation principale appelle pour initialiser le code de gestion des vues réutilisables. Nous appellerons cette méthode depuis -[UICollectionView initWithFrame:collectionViewLayout:] dans UICollectionView.m , et nous l’implémenterons dans UICollectionView+ReusableViews.m .

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 consiste à placer les variables d’instance dans le fichier d’en-tête ou 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 votre 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, en particulier si vous écrivez du code à utiliser par d’autres. Un autre problème potentiel est que si vous utilisez Objective-C ++, il est bon 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 mets toujours les variables d’instance dans l’en-tête simplement parce que c’est plus pratique pour un codeur travaillant dans Xcode. Mon conseil est de faire ce que vous estimez être plus pratique pour vous.

En gros, cela a à voir avec la visibilité de l’ivar aux sous-classes. Les sous-classes ne pourront pas accéder aux variables d’instance définies dans le bloc @implementation .

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

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 guide de programmation en Objective-C .

De la documentation:

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

Il est recommandé d’utiliser une propriété sur un object chaque fois que vous devez suivre une valeur ou un autre object.

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

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