2009-08-03 6 views
53

Dans la plupart des exemples que je vois la configuration suivante de IBOutlets:Est-ce qu'un IBOutlet doit être une propriété et synthétisé?



(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 bien (avis: absence de biens et synthétisent):



(Example B) 

FooController.h: 

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

@end 

FooController.m: 

@implementation FooController 

@end 

Y a-t-il des inconvénients de la définition IBOutlets comme dans l'exemple B? Comme des fuites de mémoire? Semble fonctionner correctement et je préfère ne pas exposer les IBOutlets en tant que propriétés publiques car elles ne sont pas utilisées en tant que telles, elles sont uniquement utilisées dans l'implémentation du contrôleur. Le définir en trois endroits sans réel besoin ne me semble pas très sec (ne vous répétez pas).

Répondre

94

Sous Mac OS X, IBOutlets sont connectés comme ceci:

  1. Rechercher une méthode called <OutletName>:. S'il existe, appelez-le.
  2. Si aucune méthode n'existe, recherchez une variable d'instance nommée <OutletName>, définissez-la sans conserver.

sur iPhone OS, IBOutlets sont connectés comme ceci:

  1. appel [objet setValue: outletValue forKey: @ "<OutletName>"]

Le comportement de la valeur définie pour la clé est de faire quelque chose comme ceci:

  1. Rechercher une méthode appelée set <OutletName>:. S'il existe, appelez-le.
  2. Si aucune méthode n'existe, recherchez une variable d'instance nommée <OutletName>, définissez-la et conservez-la.

Si vous utilisez une propriété, vous allez tomber dans le « Rechercher une méthode appelée mis <OutletName> ... » cas sur les deux plates-formes. Si vous utilisez simplement une variable d'instance, vous aurez un comportement de conservation/relâchement différent sur Mac OS X VS iPhone OS. Il n'y a rien de mal à utiliser une variable d'instance, vous avez juste besoin de 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. http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6

+0

Salut Jon, Merci pour la réponse détaillée! Très utile –

+0

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

+0

Le nom "OutletName" ci-dessus est défini comme étant tout ce qui est à côté du mot clé "IBOutlet" dans le code source. Si IBOutlet est dans @property, peu importe la variable d'instance nommée car 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 prise. Si le mot-clé IBOutlet est sur la variable d'instance et qu'un setter existe avec un nom qui ne correspond pas, le setter ne sera pas appelé. –

4

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

  • Lorsque vous utilisez champs d'instance comme points de vente, vous ne devriez pas les libérer dans dealloc.

  • Lors de l'utilisation des propriétés qui ont (conserver) attribut, vous devez libérer la propriété dans dealloc (en utilisant self.property=nil ou en libérant la variable de support). Cela rend beaucoup plus transparent ce qui se passe.

En fait, tout cela revient à la même vieille règle: « tu libération ce que vous alloc/conserviez ». Donc, dans le cas où vous utilisez un champ d'instance comme sortie, vous ne l'avez pas alloué/conservé, donc vous ne devriez pas le libérer.

+5

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

+1

Il n'est pas recommandé d'appeler "self.property = nil" dans une méthode dealloc. Vous ne devriez pas appeler 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 durring après leur désallocation, ou avant qu'il ne soit init. –

+0

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

12

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 méthode dealloc. En outre, l'exécution 64 bits peut synthétiser des variables d'instance à l'aide de déclarations de propriété. Cela signifie qu'un jour les variables d'instance (avec le IBOutlet) peuvent être omises.

Pour ces raisons, il est plus homogène et compatible pour créer toujours une propriété et utiliser le IBOutlet uniquement dans la propriété. Malheureusement, c'est aussi plus bavard.

Dans votre premier exemple, vous devez toujours libérer la sortie dans la méthode dealloc. Dans votre deuxième exemple, vous devez libérer la sortie uniquement avec iPhone OS.

1

Il est possible que ces exemples utilisent le conserver car l'exemple de code alloue par programme et initialise un UILabel, 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 (aucune propriété et aucune synthèse) avec l'IBOutlet est utilisé lorsque le développeur 'affecte' le UILabel (bouton, vue, etc.) dans l'interface Builder - en faisant glisser l'objet IBOulet vers l'étiquette ou une autre vue composant. À mon avis, l'action glisser-déposer précédente (Étiqueter sur la vue) ajoute également la sous-vue, l'étiquette à une vue - et ainsi de suite. L'étiquette est conservée par une vue; une vue est conservée par Window; La fenêtre est conservée par le propriétaire du fichier. Le propriétaire du fichier est généralement votre document qui est démarré dans le main.

Vous remarquerez que lorsque vous parcourez votre programme (en ajoutant un awakeFromNib

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

que fooLabel dispose déjà d'une adresse mémoire.

C'est parce que l'étiquette a été initialisé à partir d'un ensemble de fichiers (le fichier nib) en utilisant non init mais initWithCoder qui désérialise essentiellement le flux de fichiers à un objet - et ensuite définit la variable IBOutlet. (Nous parlons toujours de la méthode IBOutlet.)

Notez également que le susdit iOS moi thod utilise la méthode Key Value

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

qui est le modèle Observer/Observable. Ce modèle nécessite que l'objet Observable référence chaque observateur dans un ensemble/tableau. Un changement de valeur va itérer l'ensemble/tableau et mettre à jour tous les observateurs. Cet ensemble conservera déjà chaque observateur ainsi le manque de retenue dans iOS.

En outre et le reste est la spéculation.

Il semble que les cas lorsque vous utilisez Interface Builder ne puis

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

devrait peut-être changé à

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

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

Et puis il n'a pas besoin d'être libéré dans une méthode dealloc. De plus, il répondra aux exigences OSX et iOS.

C'est basé sur la logique et je pourrais manquer quelques pièces ici. Néanmoins, cela peut ne pas être important si la vue est 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 avoir sur-retenu et fuite par cycle. Et c'est parce que (spéculation encore) chaque boîte de dialogue fermée est sérialisée dans un système de fichiers et persiste ainsi x, position y et taille, avec ses sous-vues, etc Et ensuite désérialisé ... à la prochaine session ouverte (Opposé à dire minimiz ou caché.)

Questions connexes