Les IBOutlets doivent-ils être forts ou faibles sous ARC?

551

Je développe exclusivement pour iOS 5 en utilisant ARC. Les IBOutlets à UIViews (et les sous-classes) devraient strong- weakils être ou ?

Le suivant:

@property (nonatomic, weak) IBOutlet UIButton *button;

Se débarrasserait de tout cela:

- (void)viewDidUnload
{
    // ...
    self.button = nil;
    // ...
}

Y a-t-il des problèmes à le faire? Les modèles utilisent tout strongcomme les propriétés générées automatiquement créées lors de la connexion directe à l'en-tête à partir de l'éditeur «Interface Builder», mais pourquoi? Le a UIViewControllerdéjà une strongréférence à son viewqui conserve ses sous-vues.

hypercrypter
la source
11
Comme une note, IBOutletCollection()ne doit pas être weak, sinon il revient comme nil.
ohho
Xcode 8.2.1 utilise faible lors de la création d'IBOutlets via le générateur d'interface. Cependant, de nombreuses réponses ici sur SO conseillent d'utiliser Strong.
neoneye
1
@neoneye Je viens d'essayer avec xcode 8.3.2 en faisant glisser du storyboard vers un fichier rapide et il est par défautstrong
CupawnTae

Réponses:

252

Le courant recommandé les meilleures pratiques d'Apple est pour IBOutlets d'être forte à moins faible est particulièrement nécessaire pour éviter retain cycle. Comme Johannes l'a mentionné ci-dessus, cela a été commenté dans la session "Implémentation de conceptions d'interface utilisateur dans Interface Builder" de la WWDC 2015 où un ingénieur Apple a déclaré:

Et la dernière option que je veux souligner est le type de stockage, qui peut être fort ou faible. En général, vous devez renforcer votre sortie, en particulier si vous connectez une sortie à une sous-vue ou à une contrainte qui ne sera pas toujours conservée par la hiérarchie des vues. La seule fois où vous avez vraiment besoin de rendre une sortie faible, c'est si vous avez une vue personnalisée qui fait référence à quelque chose de sauvegarder la hiérarchie des vues et en général ce n'est pas recommandé.

J'ai demandé à ce sujet sur Twitter à un ingénieur de l'équipe IB et il a confirmé que strong devrait être la valeur par défaut et que les documents du développeur sont en cours de mise à jour.

https://twitter.com/_danielhall/status/620716996326350848 https://twitter.com/_danielhall/status/620717252216623104

Daniel Hall
la source
33
Est-ce vraiment vrai ou la réponse avec plus de 300 votes positifs est-elle la bonne? J'ai remarqué
Arunabh Das
4
Celui avec plus de 400 votes est correct, mais obsolète. Comme iOS 6 viewDidUnload n'est pas appelé, il n'y a donc aucun avantage à avoir des prises faibles.
kjam
7
@kjam, il y a des avantages. D'abord et avant tout, vous ne devriez pas avoir une référence forte à quelque chose que vous n'avez pas créé. Deuxièmement, le gain de performances est négligeable. Ne violez pas les meilleures pratiques de programmation simplement parce qu'un gars, même un gars bien placé, a dit que c'était 10 microsecondes plus rapide. Codez l'intention claire, n'essayez pas de jouer à l'optimisation du compilateur. Ne codez que les performances lorsqu'elles ont été mesurées dans un cas spécifique pour être un problème.
Cameron Lowell Palmer
5
Permettez-moi d'être en désaccord avec vous. «Tenir une référence forte à quelque chose que vous n'avez pas créé» se produit tout le temps dans Objective-C. C'est pourquoi il y a un comptage de référence , plutôt qu'un seul propriétaire. Avez-vous des références pour sauvegarder cette recommandation? Pourriez-vous énumérer les autres avantages des points de vente faibles?
kjam
4
Voici la vidéo WWDC mentionnée dans la réponse developer.apple.com/videos/play/wwdc2015/407/?time=1946
petrsyn
450

AVERTISSEMENT, RÉPONSE ANCIENNE : cette réponse n'est pas à jour selon WWDC 2015, pour la bonne réponse se référer à la réponse acceptée (Daniel Hall) ci-dessus. Cette réponse restera enregistrée.


Résumé de la bibliothèque des développeurs :

D'un point de vue pratique, dans iOS et OS X, les prises doivent être définies en tant que propriétés déclarées. Les sorties doivent généralement être faibles, à l'exception de celles du propriétaire du fichier aux objets de niveau supérieur dans un fichier nib (ou, dans iOS, une scène de storyboard) qui doivent être solides. Les prises que vous créez seront donc généralement faibles par défaut, car:

  • Les sorties que vous créez, par exemple, des sous-vues de la vue d'un contrôleur de vue ou de la fenêtre d'un contrôleur de fenêtre, sont des références arbitraires entre des objets qui n'impliquent pas la propriété.

  • Les sorties fortes sont fréquemment spécifiées par les classes de framework (par exemple, la sortie de vue UIViewController ou la sortie de fenêtre NSWindowController).

    @property (weak) IBOutlet MyView *viewContainerSubview;
    @property (strong) IBOutlet MyOtherClass *topLevelObject;
Alexsander Akers
la source
10
Comment avez-vous obtenu le lien "bibliothèque de développeurs" pour accéder à la partie particulière de la page doc d'Apple? Chaque fois que je crée un lien vers les documents Apple, il renvoie toujours vers le haut de la page (même si le contenu qui vous intéresse est à mi-chemin de la page). Merci.
bearMountain
68
J'ai copié le lien depuis le volet de navigation à gauche. : D
Alexsander Akers
27
Que signifie «à l'exception de ceux du propriétaire du fichier aux objets de niveau supérieur dans un fichier nib (ou, dans iOS, une scène de storyboard)»?
Van Du Tran
16
@VanDuTran - cela signifie des objets dans la NIB qui sont au niveau racine, c'est-à-dire que vous avez instancié une autre vue là-dedans qui n'est pas directement une sous-vue de la vue principale, alors elle doit avoir une référence forte.
mattjgalloway
6
Le niveau supérieur signifie que lorsque vous regardez la plume, l'objet apparaît dans la liste de gauche. Presque toutes les plumes ont une UIView en elles - cela pourrait être le seul objet de niveau supérieur. Si vous ajoutez d'autres éléments et qu'ils apparaissent dans la liste, ce sont des "objets de niveau supérieur"
David H
50

Bien que la documentation recommande d'utiliser des weakpropriétés pour les sous-vues, depuis iOS 6, il semble strongapproprié d'utiliser (le qualificatif de propriété par défaut) à la place. Cela est dû au changement dans le fait UIViewControllerque les vues ne sont plus déchargées.

  • Avant iOS 6, si vous gardiez des liens solides vers les sous-vues de la vue du contrôleur, si la vue principale du contrôleur de vue était déchargée, celles-ci resteraient sur les sous-vues aussi longtemps que le contrôleur de vue est autour.
  • Depuis iOS 6, les vues ne sont plus déchargées, mais chargées une fois, puis restent aussi longtemps que leur contrôleur est là. Des propriétés si solides n'auront aucune importance. Ils ne créeront pas non plus de cycles de référence forts, car ils pointent vers le bas le graphique de référence fort.

Cela dit, je suis déchiré entre l'utilisation

@property (nonatomic, weak) IBOutlet UIButton *button;

et

@property (nonatomic) IBOutlet UIButton *button;

dans iOS 6 et versions ultérieures:

  • L'utilisation weakindique clairement que le contrôleur ne souhaite pas être propriétaire du bouton.

  • Mais l'omission weakne fait pas de mal dans iOS 6 sans déchargement de vue et est plus courte. Certains peuvent signaler que c'est aussi plus rapide, mais je n'ai pas encore rencontré d'application trop lente à cause de l' weak IBOutletart.

  • Ne pas utiliser weakpeut être perçu comme une erreur.

Conclusion: depuis iOS 6, nous ne pouvons plus nous tromper tant que nous n'utilisons pas le déchargement de vue. Il est temps de faire la fête. ;)

Tammo Freese
la source
C'est vrai, mais vous pouvez toujours vouloir décharger la vue vous-même. Dans ce cas, vous devez définir nilmanuellement tous vos points de vente .
hypercrypt
PS: weakest un peu moins cher dans ARM64: D
hypercrypt
C'est vrai, si vous implémentez le déchargement de vue, les weakpropriétés ou les __weakvariables d'instance sont la voie à suivre. Je voulais juste souligner qu'il y a moins de risque d'erreur ici. Quant à weakêtre moins cher sur arm64, je n'ai même pas vu de problème de performance réel avec weak IBOutlets sur armv7. :)
Tammo Freese
Dans ce cas, strongcela a également du sens. strongn'est nuisible que si vous utilisez le déchargement de vues, mais qui le fait de nos jours? :)
Tammo Freese
2
@Rocotilos Le premier iPhone avait une mémoire RAM très limitée. Si je me souviens bien, 128 Mo, ce qui laisse environ 10 Mo pour l'application active. Il était crucial d'avoir une petite empreinte mémoire, d'où le déchargement de la vue. Cela a changé car nous avons maintenant de plus en plus de RAM et Apple UIViews optimisé dans iOS 6, de sorte que sur les avertissements de mémoire, beaucoup de mémoire peut être libérée sans décharger la vue.
Tammo Freese
34

Je ne vois aucun problème avec cela. Avant l'ARC, j'ai toujours créé mes IBOutlets assign, car ils sont déjà conservés par leurs superviews. Si vous les faites weak, vous ne devriez pas avoir à les supprimer dans viewDidUnload, comme vous le faites remarquer.

Une mise en garde: vous pouvez prendre en charge iOS 4.x dans un projet ARC, mais si vous le faites, vous ne pouvez pas l'utiliser weak, vous devrez donc les créer assign, auquel cas vous voudrez toujours annuler la référence viewDidUnloadpour éviter un pointeur pendant. Voici un exemple de bogue de pointeur pendant que j'ai rencontré:

Un UIViewController a un UITextField pour le code postal. Il utilise CLLocationManager pour inverser le géocodage de l'emplacement de l'utilisateur et définir le code postal. Voici le rappel du délégué:

-(void)locationManager:(CLLocationManager *)manager
   didUpdateToLocation:(CLLocation *)newLocation
          fromLocation:(CLLocation *)oldLocation {
    Class geocoderClass = NSClassFromString(@"CLGeocoder");
    if (geocoderClass && IsEmpty(self.zip.text)) {
        id geocoder = [[geocoderClass alloc] init];
        [geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
            if (self.zip && IsEmpty(self.zip.text)) {
                self.zip.text = [[placemarks objectAtIndex:0] postalCode];
            }
        }];    
    }
    [self.locationManager stopUpdatingLocation];
}

J'ai constaté que si je rejetais cette vue au bon moment et que je ne mettais pas self.zip à zéro viewDidUnload, le rappel du délégué pourrait lever une mauvaise exception d'accès sur self.zip.text.

Christopher Pickslay
la source
4
Je crois également que les weakpropriétés n'ont pas besoin d'être précisées viewDidUnload. Mais pourquoi le modèle d'Apple pour la création de points de vente comprend-il un [self setMySubview:nil]?
Yang Meyer
3
Existe-t-il des cas concrets où l'utilisation de Strong / Retained pour votre IBOutlet pourrait causer des problèmes? Ou s'agit-il simplement d'une conservation redondante, ce qui signifie un mauvais style de codage mais n'affecterait pas votre code?
Enzo Tran
1
Existe-t-il une chose telle qu'une rétention redondante? S'il y a une retenue supplémentaire, cela le fera ne pas être compté correctement et ne sera donc pas libéré dès qu'il pourrait l'être car il y a une retenue supplémentaire sur son compte de retenue.
karlbecker_com
25

IBOutletdevrait être solide, pour des raisons de performances. Voir Storyboard Reference, Strong IBOutlet, Scene Dock dans iOS 9

Comme expliqué dans ce paragraphe, les sorties vers les sous-vues de la vue du contrôleur de vue peuvent être faibles, car ces sous-vues appartiennent déjà à l'objet de niveau supérieur du fichier nib. Cependant, lorsqu'une prise est définie comme un pointeur faible et que le pointeur est défini, ARC appelle la fonction d'exécution:

id objc_storeWeak(id *object, id value);

Cela ajoute le pointeur (objet) à une table en utilisant la valeur de l'objet comme clé. Ce tableau est appelé tableau faible. ARC utilise ce tableau pour stocker tous les pointeurs faibles de votre application. À présent, lorsque la valeur de l'objet est désallouée, ARC parcourt la table faible et définit la référence faible à zéro. L'ARC peut également appeler:

void objc_destroyWeak(id * object)

Ensuite, l'objet n'est pas enregistré et objc_destroyWeak appelle à nouveau:

objc_storeWeak(id *object, nil)

Cette comptabilité associée à une référence faible peut prendre 2 à 3 fois plus de temps que la publication d'une référence forte. Ainsi, une référence faible introduit une surcharge pour le runtime que vous pouvez éviter en définissant simplement les sorties comme fortes.

Depuis Xcode 7, il suggère strong

Si vous regardez la session 407 de la WWDC 2015 sur la mise en œuvre des conceptions d'interface utilisateur dans Interface Builder , cela suggère (transcription de http://asciiwwdc.com/2015/sessions/407 )

Et la dernière option que je veux souligner est le type de stockage, qui peut être fort ou faible.

En général, vous devez renforcer votre sortie, en particulier si vous connectez une sortie à une sous-vue ou à une contrainte qui ne sera pas toujours conservée par la hiérarchie des vues.

La seule fois où vous avez vraiment besoin de rendre une sortie faible, c'est si vous avez une vue personnalisée qui fait référence à quelque chose de sauvegarder la hiérarchie des vues et en général ce n'est pas recommandé.

Je vais donc choisir fort et je clique sur connecter ce qui va générer ma sortie.

onmyway133
la source
1
Excellente réponse qui explique la raison réelle -pourquoi-
micnguyen
C'est bien et tout mais j'ai vu des fuites provenant de reconnaisseurs de gestes implémentés dans le storyboard.
thibaut noah
1
Je ne peux pas comprendre cette ligne. "La seule fois où vous avez vraiment besoin de rendre un point de vente faible, c'est si vous avez une vue personnalisée qui fait référence à quelque chose de sauvegarder la hiérarchie des vues et en général ce n'est pas recommandé." Des exemples?
user1872384
J'ai calculé le temps de déinit que prennent les faibles et les forts, et c'est exactement la même chose.
touti
Mais en bref c'est plus le cas. Les références faibles sont plus rapides.
thesummersign
20

Dans le développement iOS, le chargement NIB est un peu différent du développement Mac.

Dans le développement Mac, une IBOutlet est généralement une référence faible: si vous avez une sous-classe de NSViewController, seule la vue de niveau supérieur sera conservée et lorsque vous désallouez le contrôleur, toutes ses sous-vues et sorties sont automatiquement libérées.

UiViewController utilise le codage des valeurs clés pour définir les sorties à l'aide de références fortes. Ainsi, lorsque vous désallouez votre UIViewController, la vue de dessus sera automatiquement désallouée, mais vous devez également désallouer toutes ses sorties dans la méthode dealloc.

Dans cet article du Big Nerd Ranch , ils couvrent ce sujet et expliquent également pourquoi l'utilisation d'une référence forte dans IBOutlet n'est pas un bon choix (même si cela est recommandé par Apple dans ce cas).

Giuseppe
la source
16
Il l'explique en 2009. Avec ARC, cela a beaucoup changé.
Dafydd Williams
1
:( le lien de Big Nerd Ranch est mort… pourtant j'ai vraiment besoin de le lire. Quelqu'un sait plus de détails sur ce post, donc je peux le trouver?
Motti Shneor
@MottiShneor ne vous inquiétez pas, ce n'est pas grave car le lien était à peu près avant ARC et n'est plus pertinent.
Sergey Grischyov
18

Une chose que je souhaite souligner ici, et ce, malgré ce que les ingénieurs d'Apple ont déclaré dans leur propre vidéo WWDC 2015 ici:

https://developer.apple.com/videos/play/wwdc2015/407/

Apple ne cesse de changer d'avis sur le sujet, ce qui nous dit qu'il n'y a pas de bonne réponse unique à cette question. Pour montrer que même les ingénieurs d'Apple sont divisés sur ce sujet, jetez un coup d'œil à l'exemple de code le plus récent d'Apple, et vous verrez que certaines personnes utilisent faible, et d'autres non.

Cet exemple d'Apple Pay utilise faible: https://developer.apple.com/library/ios/samplecode/Emporium/Listings/Emporium_ProductTableViewController_swift.html#//apple_ref/doc/uid/TP40016175-Emporium_ProductTableViewController_swift

Tout comme cet exemple d'image dans l'image: https://developer.apple.com/library/ios/samplecode/AVFoundationPiPPlayer/Listings/AVFoundationPiPPlayer_PlayerViewController_swift.html#//apple_ref/doc/uid/TP40016166-AVFoundationPiPController

Tout comme l'exemple Lister: https://developer.apple.com/library/ios/samplecode/Lister/Listings/Lister_ListCell_swift.html#//apple_ref/doc/uid/TP40014701-Lister_ListCell_swift-DontLinkElementID_57

Tout comme l'exemple de l'emplacement principal: https://developer.apple.com/library/ios/samplecode/PotLoc/Listings/Potloc_PotlocViewController_swift.html#//apple_ref/doc/uid/TP40016176-Potloc_PotlocViewController_swift-

Tout comme l'exemple de prévisualisation du contrôleur de vue: https://developer.apple.com/library/ios/samplecode/ViewControllerPreviews/Listings/Projects_PreviewUsingDelegate_PreviewUsingDelegate_DetailViewController_swift.html#//apple_ref/doc_uidPort

Tout comme l'exemple HomeKit: https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Listings/HMCatalog_Homes_Action_Sets_ActionSetViewController_swift.html#//apple_ref/doc/uid/TP40015048-HMCatalogSelect

Tous ceux-ci sont entièrement mis à jour pour iOS 9 et utilisent tous des prises faibles. De cela, nous apprenons que A. Le problème n'est pas aussi simple que certains le prétendent. B. Apple a changé d'avis à plusieurs reprises, et C. Vous pouvez utiliser tout ce qui vous rend heureux :)

Un merci spécial à Paul Hudson (auteur de www.hackingwithsift.com) qui m'a donné la clarification et les références pour cette réponse.

J'espère que cela clarifie un peu mieux le sujet!

Prends soin de toi.

syedfa
la source
Je vérifie ce problème depuis un certain temps et je n'ai trouvé aucune réponse concrète. Étant donné que le lien ci-dessus suggère que les deux sont bien et vont généralement avec ce que Xcode suggère automatiquement.
subin272
6

Soyez conscient, IBOutletCollectiondevrait l'être @property (strong, nonatomic).

landonandrey
la source
3
Pourquoi pas copycar c'est un NSArray?
hypercrypt
5

Il semble que quelque chose ait changé au fil des ans et maintenant Apple recommande d'utiliser le fort en général. Les preuves de leur session WWDC se trouvent dans la session 407 - Implémentation de conceptions d'interface utilisateur dans Interface Builder et commencent à 32h30. Ma note d'après ce qu'il dit est (presque, sinon exactement, le citant):

  • les connexions de sortie en général doivent être solides, surtout si nous connectons une sous-vue ou une contrainte qui n'est pas toujours conservée par la hiérarchie des vues

  • une connexion de sortie faible peut être nécessaire lors de la création de vues personnalisées ayant une référence à quelque chose de sauvegarde dans la hiérarchie des vues et en général, il n'est pas recommandé

Dans d'autres quartiers, il devrait toujours être fort tant que certaines de nos vues personnalisées ne créent pas de cycle de conservation avec une partie de la vue dans la hiérarchie des vues.

ÉDITER :

Certains peuvent poser la question. Le conserver avec une référence forte ne crée-t-il pas un cycle de conservation car le contrôleur de vue racine et la vue propriétaire la conservent? Ou pourquoi cela a-t-il changé? Je pense que la réponse est plus tôt dans cet exposé quand ils décrivent comment les plumes sont créées à partir du xib. Il existe une pointe distincte créée pour un VC et pour la vue. Je pense que cela pourrait être la raison pour laquelle ils modifient les recommandations. Ce serait quand même bien d'obtenir une explication plus approfondie d'Apple.

Julian Król
la source
4

Je pense que les informations les plus importantes sont les suivantes: les éléments de xib sont automatiquement en sous-vues. Subviews est NSArray. NSArray possède ses éléments. etc ont des pointeurs forts sur eux. Donc, dans la plupart des cas, vous ne voulez pas créer un autre pointeur fort (IBOutlet)

Et avec ARC, vous n'avez rien à faire viewDidUnload

kraag22
la source