39 votes

Que se passe-t-il si je ne conserve pas IBOutlet ?

Si je fais ça :

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
    IBOutlet UITextField *usernameField;
}

au lieu de ça :

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
    UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;

Est-ce que quelque chose de grave va se produire ? Je sais que dans le second cas, le champ est conservé, mais cela fait-il une différence puisque la plume possède le champ ? Le champ disparaîtra-t-il sans le maintien ? et dans quelles circonstances ? Le code dans le premier cas fonctionne, je me demandais si c'était un problème ou non en termes de gestion de la mémoire.

71voto

Joey Hagedorn Points 2015

Pour des raisons de clarté et de cohérence, il est recommandé de déclarer des propriétés pour tous vos IBOutlets. Les détails sont expliqués dans la section Guide de programmation de la gestion de la mémoire . L'essentiel est que, lorsque vos objets NIB ne sont pas archivés, le code de chargement de la NIB passe en revue et définit tous les IBOutlets en utilisant setValue:forKey :. Lorsque vous déclarez le comportement de gestion de la mémoire sur la propriété, il n'y a aucun mystère quant à ce qui se passe. Si la vue est déchargée, mais que vous avez utilisé une propriété déclarée comme étant conservée, vous disposez toujours d'une référence valide à votre champ de texte.

Un exemple plus concret serait peut-être utile pour indiquer pourquoi vous devriez utiliser une propriété de retenue :

Je vais faire quelques hypothèses sur le contexte dans lequel vous travaillez - je vais supposer que le UITextField ci-dessus est une sous-vue d'une autre vue qui est contrôlée par un UIViewController. Je vais supposer qu'à un moment donné, la vue est hors de l'écran (peut-être est-elle utilisée dans le contexte d'un UINavigationController), et qu'à un moment donné votre application reçoit un avertissement de mémoire.

Supposons que votre sous-classe UIViewController doive accéder à sa vue pour l'afficher à l'écran. À ce stade, le fichier nib sera chargé et les propriétés de chaque IBOutlet seront définies par le code de chargement de la nib à l'aide de setValue:forKey :. Les propriétés importantes à noter ici sont la vue de premier niveau qui sera définie dans la propriété view de UIViewController (qui conservera cette vue de premier niveau) et votre UITextField, qui sera également conservé. S'il est simplement défini, le code de chargement de la nib lui attribuera un retain, sinon la propriété l'aura retenu. L'UITextField sera également une sous-vue de l'UIView de niveau supérieur, il aura donc un retain supplémentaire sur lui, étant dans le tableau des sous-vues de la vue de niveau supérieur, donc à ce stade le champ de texte a été retenu deux fois.

À ce stade, si vous souhaitez remplacer le champ de texte par un programme, vous pouvez le faire. L'utilisation de la propriété rend la gestion de la mémoire plus claire ici ; il suffit de définir la propriété avec un nouveau champ de texte à libération automatique. Si vous n'aviez pas utilisé la propriété, vous devez vous rappeler de la libérer et, éventuellement, de conserver le nouveau champ. À ce stade, il est quelque peu ambigu de savoir à qui appartient ce nouveau champ de texte, car la sémantique de la gestion de la mémoire n'est pas contenue dans le paramètre.

Supposons maintenant qu'un autre contrôleur de vue soit poussé sur la pile du contrôleur UINavigation, de sorte que cette vue ne soit plus au premier plan. Dans le cas d'un avertissement de mémoire, la vue de ce contrôleur de vue hors écran sera déchargée. À ce stade, la propriété view de la UIView de niveau supérieur sera annulée, elle sera libérée et désallouée.

Parce que le champ UITextField a été défini comme une propriété qui a été conservée, le champ UITextField n'est pas désalloué, comme il l'aurait été si sa seule propriété conservée avait été celle du tableau des sous-vues de la vue de niveau supérieur.

Si, au contraire, la variable d'instance de l'UITextField n'avait pas été définie via une propriété, elle serait également présente, car le code de chargement de la plume l'aurait conservée lors de la définition de la variable d'instance.

Un point intéressant que cela met en évidence est que, puisque le UITextField est également conservé par la propriété, vous ne voudrez probablement pas le garder en cas d'avertissement de mémoire. Pour cette raison, vous devriez annuler la propriété dans la méthode -[UIViewController viewDidUnload]. Cela permettra de se débarrasser de la libération finale sur le UITextField et de le désallouer comme prévu. Si vous utilisez la propriété, vous devez vous souvenir de la libérer explicitement. Bien que ces deux actions soient fonctionnellement équivalentes, l'intention est différente.

Si, au lieu de remplacer le champ de texte, vous avez choisi de le supprimer de la vue, il se peut que vous l'ayez déjà supprimé de la hiérarchie de la vue et que vous ayez mis la propriété à nil, ou libéré le champ de texte. Bien qu'il soit possible d'écrire un programme correct dans ce cas, il est facile de commettre l'erreur de sur-libérer le champ de texte dans la méthode viewDidUnload. La libération excessive d'un objet est une erreur qui provoque un crash, mais le fait de redonner la valeur nil à une propriété qui l'est déjà ne l'est pas.

Ma description a peut-être été trop verbeuse, mais je ne voulais omettre aucun détail du scénario. En suivant simplement les directives, vous éviterez les problèmes lorsque vous rencontrerez des situations plus complexes.

Il convient en outre de noter que le comportement de la gestion de la mémoire diffère sur Mac OS X sur le bureau. Sur le bureau, la définition d'une IBOutlet sans setter ne conserve pas la variable d'instance, mais utilise à nouveau le setter s'il est disponible.

11voto

zpasternack Points 10563

Déclarer un IBOutlet, du point de vue de la gestion de la mémoire, n'apporte rien (IBOutlet est littéralement #défini comme rien). La seule raison d'inclure IBOutlet dans la déclaration est que vous avez l'intention de le connecter dans Interface Builder (c'est à cela que sert la déclaration IBOutlet, un indice pour IB).

Maintenant, la seule raison de créer une @propriété pour une variable d'instance est si vous avez l'intention de les assigner de manière programmatique. Si ce n'est pas le cas (c'est-à-dire si vous ne faites que mettre en place votre interface utilisateur dans IB), il n'est pas important de créer une propriété ou non. Il n'y a aucune raison de le faire, IMO.

Pour en revenir à votre question. Si vous configurez uniquement cet ivar (usernameField) dans IB, ne vous préoccupez pas de la propriété, elle n'aura aucune incidence. Si vous DEVEZ créer une propriété pour usernameField (parce que vous le créez par programme), il faut absolument créer une propriété pour ce champ, et il faut absolument que la propriété soit conservée si c'est le cas.

6voto

Freeman Points 1725

En fait, il existe deux modèles :

L'ANCIEN MODÈLE

Ce modèle était celui d'avant Objective-C 2.0 et hérité de Mac OS X. Il fonctionne toujours, mais vous ne devez pas déclarer de propriétés pour modifier les ivars. C'est-à-dire :

@interface StrokeWidthController : UIViewController {
    IBOutlet UISlider* slider;
    IBOutlet UILabel* label;
    IBOutlet StrokeDemoView* strokeDemoView;
    CGFloat strokeWidth;
}
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

Dans ce modèle, vous ne conservez pas les ivars IBOutlet, mais vous devez les libérer. C'est-à-dire :

- (void)dealloc {
    [slider release];
    [label release];
    [strokeDemoView release];
    [super dealloc];
}

LE NOUVEAU MODÈLE

Vous devez déclarer des propriétés pour les variables IBOutlet :

@interface StrokeWidthController : UIViewController {
    IBOutlet UISlider* slider;
    IBOutlet UILabel* label;
    IBOutlet StrokeDemoView* strokeDemoView;
    CGFloat strokeWidth;
}
@property (retain, nonatomic) UISlider* slider;
@property (retain, nonatomic) UILabel* label;
@property (retain, nonatomic) StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

En outre, vous devez libérer les variables dans dealloc :

- (void)dealloc {
    self.slider = nil;
    self.label = nil;
    self.strokeDemoView = nil;
    [super dealloc];
}

En outre, dans les plateformes non fragiles, vous pouvez supprimer les ivars :

@interface StrokeWidthController : UIViewController {
    CGFloat strokeWidth;
}
@property (retain, nonatomic) IBOutlet UISlider* slider;
@property (retain, nonatomic) IBOutlet UILabel* label;
@property (retain, nonatomic) IBOutlet StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

LA CHOSE ÉTRANGE

Dans les deux cas, les sorties sont définies en appelant setValue:forKey :. Le runtime vérifie en interne (en particulier _decodeObjectBinary) si la méthode setter existe. Si elle n'existe pas (seul l'ivar existe), il envoie un retain supplémentaire à l'ivar. Pour cette raison, vous ne devez pas retenir l'IBOutlet s'il n'existe pas de méthode setter.

2voto

Jasarien Points 35353

Il n'y a aucune différence entre le fonctionnement de ces deux définitions d'interface jusqu'à ce que vous commenciez à utiliser les accesseurs fournis par la propriété.

Dans les deux cas, vous devrez toujours libérer et mettre à néant l'IBOutlet dans vos méthodes dealloc ou viewDidUnload.

L'IBOutlet pointe vers un objet instancié dans un fichier XIB. Cet objet appartient à l'objet File's Owner du fichier XIB (généralement le contrôleur de vue dans lequel l'IBOutlet est déclaré).

Comme l'objet est créé à la suite du chargement du XIB, son nombre de retenue est de 1 et il appartient au propriétaire du fichier, comme mentionné ci-dessus. Cela signifie que le propriétaire du fichier est responsable de la libération de l'objet lorsqu'il est désalloué.

L'ajout de la déclaration de propriété avec l'attribut retain indique simplement que la méthode setter doit conserver l'objet transmis pour être défini - ce qui est la bonne façon de procéder. Si vous n'avez pas spécifié retain dans la déclaration de propriété, l'IBOutlet pourrait pointer vers un objet qui n'existe peut-être plus, car il a été libéré par son propriétaire ou libéré automatiquement à un moment donné du cycle de vie du programme. Le fait de le conserver empêche la désaffectation de cet objet jusqu'à ce que vous en ayez fini avec lui.

1voto

Dharmateja Points 11

Les objets du fichier nib sont créés avec un compte de conservation de 1 et sont ensuite libérés automatiquement. Lorsqu'il reconstruit la hiérarchie des objets UIKit rétablit les connexions entre les objets à l'aide de setValue:forKey :, qui utilise la méthode setter disponible ou conserve l'objet par défaut si aucune méthode n'est disponible. Cela signifie que tout objet pour lequel vous disposez d'une sortie reste valide. Toutefois, si vous ne stockez pas d'objets de niveau supérieur dans des points de vente, vous devez conserver soit le tableau renvoyé par la méthode loadNibNamed:owner:options :, soit les objets contenus dans le tableau pour éviter que ces objets ne soient libérés prématurément.

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