53 votes

Un IBOutlet doit-il être une propriété et synthétisé ?

Dans la plupart des exemples, je vois la configuration suivante des IBOutlets :

(Example A)

FooController.h:

@interface FooController : UIViewController {
    UILabel *fooLabel;
}

@property (nonatomic, retain) IBOutlet UILabel *fooLabel;

@end

FooController.m:

@implementation FooController

@synthesize fooLabel;

@end

Mais cela fonctionne aussi très bien (remarque : pas de propriété et pas de synthétisation) :

(Example B)

FooController.h:

@interface FooController : UIViewController {
    IBOutlet UILabel *fooLabel;
}

@end

FooController.m:

@implementation FooController

@end

Y a-t-il des inconvénients à définir les IBOutlets comme dans l'exemple B ? Comme des fuites de mémoire ? Cela semble bien fonctionner et je préfère ne pas exposer les IBOutlets en tant que propriétés publiques car ils ne sont pas utilisés en tant que tels, mais uniquement dans l'implémentation du contrôleur. Le fait de le définir à trois endroits sans réel besoin ne me semble pas très DRY (Don't Repeat Yourself).

96voto

Jon Hess Points 10529

Sous Mac OS X, les IBOutlets sont connectées comme suit :

  1. Recherchez une méthode appelée set<OutletName> :. Si elle existe, appelez-la.
  2. Si aucune méthode n'existe, recherchez une variable d'instance nommée <OutletName> et définissez-la. sans retenir .

Sur l'iPhone OS, les IBOutlets sont connectées comme ceci :

  1. call [object setValue:outletValue forKey:@"<OutletName>"]

Le comportement de set value for key est de faire quelque chose comme ceci :

  1. Recherchez une méthode appelée set<OutletName> :. Si elle existe, appelez-la.
  2. Si aucune méthode n'existe, cherchez une variable d'instance nommée <OutletName>, définissez-la et retenir il.

Si vous utilisez une propriété, vous tomberez dans la catégorie " Recherchez une méthode appelée set<OutletName> :... "sur les deux plateformes. Si vous utilisez simplement une variable d'instance, vous aurez un comportement de conservation/libération différent sur Mac OS X et sur iPhone OS. Il n'y a rien de mal à utiliser une variable d'instance, vous devez simplement gérer cette différence de comportement lorsque vous passez d'une plateforme à l'autre.

Voici un lien vers une documentation complète sur ce sujet. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6

0 votes

Bonjour Jon, merci pour cette réponse détaillée ! Très utile

0 votes

Que se passe-t-il si le nom de la variable est différent du nom de la propriété ? Est-ce important s'il est différent ?

0 votes

Le nom "OutletName" ci-dessus est défini comme ce qui se trouve à côté du mot clé "IBOutlet" dans le code source. Si IBOutlet est dans la @propriété, le nom de la variable d'instance importe peu puisqu'un setter sera trouvé. Si, pour une raison quelconque, un setter n'existait pas, une exception serait levée lors de la connexion de la sortie. Si le mot-clé IBOutlet se trouve sur la variable d'instance et qu'il existe un setter dont le nom ne correspond pas, le setter ne sera pas appelé.

12voto

Freeman Points 1725

Sous Mac OS X, les IBOutlets ne sont pas conservés par défaut. C'est le contraire du comportement sur iPhone OS : sur iPhone OS, si vous ne déclarez pas une propriété, elle est conservée et vous devez libérer cette propriété dans la fonction dealloc méthode. De plus, le runtime 64 bits peut synthétiser les variables d'instance en utilisant des déclarations de propriétés. Cela signifie qu'un jour, les variables d'instance (avec l'attribut IBOutlet ) peut être omis.

Pour ces raisons, il est plus homogène et compatible de créer toujours une propriété et d'utiliser l'option IBOutlet uniquement dans la propriété. Malheureusement, elle est aussi plus verbeuse.

Dans votre premier exemple, vous devez toujours relâcher la prise dans les dealloc méthode. Dans votre deuxième exemple, vous devez libérer la sortie uniquement avec l'iPhone OS.

4voto

Philippe Leybaert Points 62715

Le résultat final est exactement le même, mais vous devez garder quelques éléments à l'esprit :

  • Lorsque vous utilisez des champs d'instance en tant que points de vente, vous ne devez PAS les libérer dans les champs dealloc .

  • Lorsque vous utilisez des propriétés qui ont l'attribut (retain), vous devez libérer la propriété dans la section dealloc (en utilisant self.property=nil ou en libérant la variable de support). Cela permet d'être beaucoup plus transparent quant à ce qui se passe.

En fait, tout se résume à la même vieille règle : " tu libéreras ce que tu as alloué/retenu ". Donc, dans le cas où vous utilisez un champ d'instance comme outlet, vous ne l'avez pas alloué/rétenu, donc vous ne devez pas le libérer.

5 votes

Ce conseil est correct pour Mac OS X, mais pas pour iPhone OS. Voir ma réponse ci-dessous.

1 votes

Appeler "self.property=nil" dans une méthode dealloc n'est pas une bonne pratique à adopter. Vous ne devriez pas appeler de méthodes depuis init ou dealloc, si vous êtes sous-classé, votre sous-classe ne s'attend probablement pas à ce que ces setters soient appelés pendant qu'il est dealloced, ou avant qu'il soit initted.

0 votes

C'est le seul moyen de libérer les propriétés auto-retenues qui utilisent la synthèse des variables d'instance (sans déclaration explicite des champs de sauvegarde). Vous n'avez pas le choix, bonne pratique ou pas.

1voto

Gabe Rainbow Points 751

Il est possible que ces exemples utilisent le retain parce que le code de l'exemple alloue et initialise un UILabel de manière programmatique, puis l'ajoute à l'UIView. C'est le cas pour de nombreux exemples, car apprendre à utiliser Interface Builder n'est souvent pas leur but.

Le deuxième exemple (pas de propriété et pas de synthèse) avec l'IBOutlet est utilisé lorsque le développeur "assigne" l'UILabel (bouton, vue, etc.) dans l'Interface Builder -- en faisant glisser l'IBOulet vers l'étiquette ou un autre composant de vue. À mon avis, l'action précédente de glisser-déposer (Label sur la vue) ajoute également la sous-vue, le Label à une vue -- et ainsi de suite. L'étiquette est conservée par une vue ; une vue est conservée par la fenêtre ; la fenêtre est conservée par le propriétaire du fichier. Le propriétaire du fichier est généralement le document qui est démarré dans la fenêtre principale.

Vous remarquerez que lorsque vous passez à l'étape suivante de votre programme (en ajoutant un awakeFromNib

- (void)awakeFromNib
{
    [fooLabel blahblah];
}

que fooLabel a déjà une adresse mémoire.

C'est parce que l'étiquette a été initialisée à partir d'un fichier groupé (le fichier nib) en utilisant non pas init mais initWithCoder. Ce qui a pour effet de désérialiser le flux de données vers un objet, puis de définir la variable IBOutlet. (Nous parlons toujours de la méthode IBOutlet).

Notez également que la méthode iOS mentionnée ci-dessus utilise la méthode Clé-Valeur

  call [object setValue:outletValue forKey:@"<OutletName>"]

qui est le modèle Observer/Observable. Ce modèle exige que l'objet Observable fasse référence à chaque Observer dans un Set/Array. Un changement de valeur va itérer l'ensemble/le tableau et mettre à jour également tous les observateurs. Cet ensemble retiendra déjà chaque Observateur, d'où l'absence de retain dans iOS.

Plus loin et le reste n'est que spéculation.

Il semble que dans les cas où vous utilisez Interface Builder, alors

 @property (nonatomic, retain) IBOutlet UILabel *fooLabel;

devrait éventuellement être modifié en

@property (nonatomic, weak) IBOutlet UILabel *fooLabel;

ou @property (nonatomic, assign) IBOutlet UILabel *fooLabel ;

Et il n'est pas nécessaire de le libérer dans une méthode dealloc. De plus, cela répondra aux exigences d'OSX et d'iOS.

C'est basé sur la logique et il se peut que je manque des éléments ici.

Néanmoins, il peut être indifférent que la vue soit persistante pendant toute la durée de vie de votre programme. Alors qu'une étiquette dans une boîte de dialogue modale (ouvrir, fermer, ouvrir, fermer) peut en fait être sur-réservée et fuir par cycle. Et c'est parce que (encore une spéculation) chaque boîte de dialogue fermée est sérialisée dans un système de fichiers et persiste donc la position x, y et la taille, ainsi que ses sous-vues, etc. Et ensuite désérialisée ... à l'ouverture de la session suivante (par opposition à minimiser ou cacher).

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