Allons-nous toujours utiliser [soi sans propriétaire] à l'intérieur de la fermeture dans Swift

467

Dans la session 403 de la WWDC 2014 Intermediate Swift et transcription , il y avait la diapositive suivante

entrez la description de l'image ici

L'orateur a dit que dans ce cas, si nous ne l'utilisons pas [unowned self], ce sera une fuite de mémoire. Cela signifie-t-il que nous devrions toujours utiliser la [unowned self]fermeture intérieure?

Sur la ligne 64 de ViewController.swift de l'application Swift Weather , je n'utilise pas [unowned self]. Mais je mets à jour l'interface utilisateur en utilisant certains @IBOutlets comme self.temperatureet self.loadingIndicator. Cela peut être OK car tous les @IBOutlets que j'ai définis le sont weak. Mais pour la sécurité, devrions-nous toujours utiliser [unowned self]?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}
Jake Lin
la source
le lien de l'image est rompu
Daniel Gomez Rico
@ DanielG.R. Merci, je peux le voir. i.stack.imgur.com/Jd9Co.png
Jake Lin
2
À moins que je ne me trompe, l'exemple donné dans la diapositive est incorrect - onChangedevrait être une [weak self]fermeture, car c'est une propriété publique (en interne, mais quand même), donc un autre objet pourrait obtenir et stocker la fermeture, en gardant l'objet TempNotifier autour (indéfiniment si l'objet utilisateur n'a pas lâché la onChangefermeture jusqu'à ce qu'il voit le TempNotifierdisparu, via sa propre référence faible à la TempNotifier) . Si var onChange …c'était le cas , private var onChange …ce [unowned self]serait correct. Je n'en suis cependant pas sûr à 100%; quelqu'un me corrige s'il vous plaît si je me trompe.
Slipp D. Thompson, du
@Jake Lin `var onChange: (Int) -> Void = {}` les accolades représentent-elles une fermeture vide? identique à la définition d'un tableau vide avec []? Je ne trouve pas l'explication dans les documents Apple.
bibscy
@bibscy yes, {}est la fermeture vide (l'instance de la fermeture) par défaut (ne fait rien), (Int) -> Voidest la définition de la fermeture.
Jake Lin

Réponses:

871

Non, il y a certainement des moments où vous ne voudriez pas utiliser [unowned self] . Parfois, vous voulez que la fermeture se capture elle-même afin de s'assurer qu'elle est toujours là au moment où la fermeture est appelée.

Exemple: effectuer une demande de réseau asynchrone

Si vous faites une demande de réseau asynchrone , vous ne voulez la fermeture de retenir selfpour quand la demande se termine. Cet objet peut avoir été autrement désalloué, mais vous voulez toujours pouvoir gérer la fin de la demande.

Quand utiliser unowned self ouweak self

La seule fois où vous voulez vraiment utiliser [unowned self]ou [weak self]c'est quand vous créez un cycle de référence solide . Un solide cycle de référence est lorsqu'il y a une boucle de propriété où les objets finissent par se posséder (peut-être par le biais d'un tiers) et donc ils ne seront jamais désalloués car ils s'assurent tous les deux de rester ensemble.

Dans le cas spécifique d'une fermeture, il vous suffit de réaliser que toute variable référencée à l'intérieur de celle-ci est "détenue" par la fermeture. Tant que la fermeture est autour, ces objets sont garantis autour. La seule façon d'arrêter cette propriété est de faire le [unowned self]ou [weak self]. Donc, si une classe possède une fermeture et que cette fermeture capture une référence forte à cette classe, alors vous avez un cycle de référence solide entre la fermeture et la classe. Cela inclut également si la classe possède quelque chose qui possède la fermeture.

Plus précisément dans l'exemple de la vidéo

Dans l'exemple de la diapositive, TempNotifierpossède la fermeture via la onChangevariable membre. Si elles ne déclarent selfque unownedla fermeture aurait aussi propre selfcréation d' un cycle de référence forte.

Différence entre unownedetweak

La différence entre unownedet weakest celle qui weakest déclarée comme étant facultative alors que ce unownedn'est pas le cas. En le déclarant, weakvous pouvez gérer le cas où il pourrait être nul à l'intérieur de la fermeture à un moment donné. Si vous essayez d'accéder à une unownedvariable qui se trouve être nulle, cela bloquera tout le programme. Donc, utilisez uniquement unownedlorsque vous êtes certain que la variable sera toujours présente pendant la fermeture.

drewag
la source
1
Salut. Très bonne réponse. J'ai du mal à comprendre le moi sans propriétaire. Une raison d'utiliser la faiblesse de soi étant simplement que «le moi devient une option» ne me suffit pas. Pourquoi voudrais-je spécifiquement utiliser 'self non propriétaire' stackoverflow.com/questions/32936264/…
19
@robdashnash, L'avantage d'utiliser soi-même sans propriétaire est que vous n'avez pas à déballer un code optionnel qui peut être inutile si vous êtes sûr de par sa conception, qu'il ne sera jamais nul. En fin de compte, le soi sans propriétaire est utilisé pour la brièveté et peut-être aussi comme un indice pour les futurs développeurs que vous ne vous attendez jamais à une valeur nulle.
attiré
77
Un cas d'utilisation [weak self]dans une demande de réseau asynchrone se trouve dans un contrôleur de vue où cette demande est utilisée pour remplir la vue. Si l'utilisateur se retire, nous n'avons plus besoin de remplir la vue, ni de référence au contrôleur de vue.
David James
1
weakles références sont également définies nillorsque l'objet est désalloué. unownedles références ne le sont pas.
BergQuester
1
Je suis un peu confus. unownedest utilisé pendant non-Optionalque weakest utilisé pour Optionalnotre selfest Optionalou non-optional?
Muhammad Nayab
193

Mise à jour 11/2016

J'ai écrit un article sur cette extension de cette réponse (en examinant SIL pour comprendre ce que fait ARC), consultez-le ici .

Réponse originale

Les réponses précédentes ne donnent pas vraiment de règles simples sur le moment de les utiliser et pourquoi, alors permettez-moi d'ajouter quelques choses.

La discussion non possédée ou faible se résume à une question de durée de vie de la variable et de la fermeture qui y fait référence.

rapide faible vs sans propriétaire

Scénarios

Vous pouvez avoir deux scénarios possibles:

  1. La fermeture a la même durée de vie de la variable, donc la fermeture ne sera accessible que jusqu'à ce que la variable soit accessible . La variable et la fermeture ont la même durée de vie. Dans ce cas, vous devez déclarer la référence comme non possédée . Un exemple courant est celui [unowned self]utilisé dans de nombreux exemples de petites fermetures qui font quelque chose dans le contexte de leurs parents et qui ne sont référencés nulle part ailleurs ne survivent pas à leurs parents.

  2. La durée de vie de fermeture est indépendante de celle de la variable, la fermeture peut toujours être référencée lorsque la variable n'est plus joignable. Dans ce cas, vous devez déclarer la référence faible et vérifier qu'elle n'est pas nulle avant de l'utiliser (ne forcez pas le déballage). Un exemple courant de ceci est le que [weak delegate]vous pouvez voir dans certains exemples de fermeture référençant un objet délégué complètement indépendant (sur la durée de vie).

Utilisation réelle

Alors, lequel utiliserez-vous / devriez-vous utiliser la plupart du temps?

Citant Joe Groff de Twitter :

Unown est plus rapide et permet l'immuabilité et la non-optionnalité.

Si vous n'avez pas besoin de faiblesse, ne l'utilisez pas.

Vous trouverez plus d'informations sur *le fonctionnement interne sans propriétaire ici .

* Habituellement également appelé non propriétaire (sûr) pour indiquer que les vérifications d'exécution (qui conduisent à un crash pour les références non valides) sont effectuées avant d'accéder à la référence non possédée.

Umberto Raimondi
la source
26
Je suis fatigué d'entendre l'explication du perroquet "utiliser la semaine si le soi peut être nul, utiliser sans propriétaire quand il ne peut jamais être nul". Ok nous l'avons eu - entendu un million de fois! Cette réponse approfondit en fait quand self peut être nul en anglais simple, ce qui répond directement à la question du PO. Merci pour cette grande explication !!
TruMan1
Merci @ TruMan1, j'écris actuellement un article à ce sujet qui finira bientôt sur mon blog, mettra à jour la réponse avec un lien.
Umberto Raimondi
1
Belle réponse, très pratique. Je suis inspiré pour passer de certains de mes vars faibles sensibles aux performances à ceux qui ne le sont pas maintenant.
original_username
"La durée de vie de fermeture est indépendante de celle de la variable" Avez-vous une faute de frappe ici?
Honey
1
Si une fermeture a toujours la même durée de vie que l'objet parent, le décompte de références ne serait-il pas pris en charge de toute façon lorsque l'objet est détruit? Pourquoi ne pouvez-vous pas simplement utiliser «soi» dans cette situation au lieu de vous embêter avec des inconnus ou des faibles?
LegendLength
105

J'ai pensé ajouter des exemples concrets spécifiquement pour un contrôleur de vue. De nombreuses explications, pas seulement ici sur Stack Overflow, sont vraiment bonnes, mais je travaille mieux avec des exemples réels (@drewag a pris un bon départ à ce sujet):

  • Si vous avez une fermeture pour gérer une réponse d'un réseau, utilisez-la weak, car elle a une longue durée de vie. Le contrôleur de vue peut se fermer avant la fin de la demande, il selfne pointe donc plus vers un objet valide lorsque la fermeture est appelée.
  • Si vous avez une fermeture qui gère un événement sur un bouton. Cela peut être unowneddû au fait que dès que le contrôleur de vue disparaît, le bouton et tous les autres éléments auxquels il peut faire référence selfdisparaissent en même temps. Le bloc de fermeture disparaîtra également en même temps.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }
possen
la source
17
Celui-ci a besoin de plus de votes positifs. Deux exemples solides montrant comment une fermeture de bouton n'existera pas en dehors de la durée de vie du contrôleur de vue, et peut donc utiliser sans propriétaire, mais la plupart des appels réseau qui mettent à jour l'interface utilisateur doivent être faibles.
Tim Fuqua
2
Alors juste pour clarifier, utilisons-nous toujours sans propriétaire ou faible lorsque nous nous appelons dans un bloc de fermeture? Ou y a-t-il un moment où nous n'appellerons pas faible / sans propriétaire? Si oui, pourriez-vous également en donner un exemple?
luke
Merci beaucoup.
Shawn Baek
1
Cela m'a permis de mieux comprendre [moi faible] et [moi sans propriétaire] Merci beaucoup @possen!
Tommy
C'est bien. que se passe-t-il si j'ai une animation basée sur l'interaction de l'utilisateur, mais qui prend du temps à terminer. Et puis l'utilisateur se déplace vers un autre viewController. Je suppose que dans ce cas, je devrais toujours utiliser weakplutôt que unownednon?
Honey
67

Si le self peut être nul dans la fermeture, utilisez [self faible] .

Si l' auto ne sera jamais nul dans la fermeture, utilisez [auto sans propriétaire] .

La documentation Apple Swift contient une grande section avec des images expliquant la différence entre l'utilisation de fermetures fortes , faibles et sans propriétaire :

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

TenaciousJay
la source
50

Voici des citations brillantes des forums de développeurs Apple qui décrivent de délicieux détails:

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)est une référence non propriétaire qui affirme lors de l'accès que l'objet est toujours vivant. C'est un peu comme une référence facultative faible qui est implicitement déballée à x!chaque accès. unowned(unsafe)est comme __unsafe_unretaineddans ARC - c'est une référence qui ne possède pas, mais il n'y a pas de contrôle d'exécution que l'objet est toujours vivant à l'accès, donc les références pendantes atteindront la mémoire des ordures. unownedest toujours synonyme de unowned(safe)actuellement, mais l'objectif est qu'il sera optimisé unowned(unsafe)dans les -Ofast générations lorsque les vérifications d'exécution sont désactivées.

unowned contre weak

unownedutilise en fait une implémentation beaucoup plus simple que weak. Les objets Swift natifs comportent deux nombres de références , et les unowned références remplacent le nombre de références non possédées au lieu du nombre de références fort . L'objet est désinitialisé lorsque son nombre de références fort atteint zéro, mais il n'est en fait pas désalloué jusqu'à ce que le nombre de références sans propriétaire également zéro. Cela fait que la mémoire est conservée un peu plus longtemps lorsqu'il y a des références non possédées, mais ce n'est généralement pas un problème lorsqueunowned soit utilisé parce que les objets associés devraient avoir des durées de vie presque égales de toute façon, et c'est beaucoup plus simple et plus bas que le côté- implémentation basée sur table utilisée pour la mise à zéro des références faibles.

Mise à jour: Dans Swift moderne weakutilise en interne le même mécanisme que unownedfait . Cette comparaison est donc incorrecte car elle compare Objective-C weakavec Swift unonwed.

Les raisons

Quel est le but de maintenir la mémoire en vie après que les références de propriété aient atteint 0? Que se passe-t-il si le code tente de faire quelque chose avec l'objet à l'aide d'une référence non possédée après sa désinitialisation?

La mémoire est maintenue en vie afin que ses comptes de conservation soient toujours disponibles. De cette façon, lorsque quelqu'un tente de conserver une référence forte à l'objet non possédé, le runtime peut vérifier que le nombre de références fortes est supérieur à zéro afin de s'assurer qu'il est sûr de conserver l'objet.

Qu'advient-il des références propriétaires ou non possédées par l'objet? Leur durée de vie est-elle découplée de l'objet lorsqu'il est désinitialisé ou leur mémoire est-elle également conservée jusqu'à ce que l'objet soit désalloué après la libération de la dernière référence non possédée?

Toutes les ressources détenues par l'objet sont libérées dès que la dernière référence forte de l'objet est libérée et son déinit est exécuté. Les références non possédées ne font que garder la mémoire en vie - à part l'en-tête avec le nombre de références, son contenu est indésirable.

Excité, hein?

Valentin Shergin
la source
38

Il y a d'excellentes réponses ici. Mais les récents changements apportés à la façon dont Swift implémente les références faibles devraient changer le moi faible de chacun contre les décisions d'utilisation de soi sans propriétaire. Auparavant, si vous aviez besoin des meilleures performances, utiliser le soi sans propriétaire était supérieur au soi faible, tant que vous pouviez être certain que le soi ne serait jamais nul, car accéder au soi sans propriétaire est beaucoup plus rapide que d'accéder au soi faible.

Mais Mike Ash a documenté comment Swift a mis à jour l'implémentation de vars faibles pour utiliser les tables d'appoint et comment cela améliore considérablement les faibles performances personnelles.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

Maintenant qu'il n'y a pas de pénalité de performance importante pour un soi faible, je pense que nous devrions par défaut l'utiliser à l'avenir. L'avantage du self faible est qu'il est facultatif, ce qui facilite l'écriture de code plus correct, c'est essentiellement la raison pour laquelle Swift est un si bon langage. Vous pensez peut-être que vous savez quelles situations sont sûres pour l'utilisation de soi sans propriétaire, mais mon expérience en examinant beaucoup de code d'autres développeurs est, la plupart ne le savent pas. J'ai corrigé de nombreux plantages où le self sans propriétaire était désalloué, généralement dans des situations où un thread d'arrière-plan se termine après la désallocation d'un contrôleur.

Les bogues et les plantages sont les parties de programmation les plus chronophages, douloureuses et coûteuses. Faites de votre mieux pour écrire le code correct et évitez-les. Je recommande de faire une règle de ne jamais forcer les options de déballage et de ne jamais utiliser le soi sans propriétaire au lieu du soi faible. Vous ne perdrez rien en manquant les moments où le déballage forcé et le soi sans propriétaire sont en fait sûrs. Mais vous gagnerez beaucoup en éliminant les plantages et les bogues difficiles à trouver et à déboguer.

SafeFastExpressive
la source
Merci pour la mise à jour et Amen sur le dernier paragraphe.
devise
1
Donc, après les nouveaux changements Y a-t-il jamais un moment où weakne peut pas être utilisé à la place d'un unowned?
Honey
4

Selon Apple-doc

  • Les références faibles sont toujours de type facultatif et deviennent automatiquement nulles lorsque l'instance à laquelle elles font référence est désallouée.

  • Si la référence capturée ne deviendra jamais nulle, elle doit toujours être capturée comme référence non possédée, plutôt que comme référence faible

Exemple -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }
Jack
la source
0

Si rien de ce qui précède n'a de sens:

tl; dr

Tout comme un implicitly unwrapped optional, Si vous pouvez garantir que la référence ne sera pas nulle à son point d'utilisation, utilisez-la sans propriétaire. Sinon, alors vous devriez utiliser faible.

Explication:

J'ai récupéré ce qui suit ci-dessous à: faible lien sans propriétaire . D'après ce que j'ai rassemblé, le moi sans propriétaire ne peut pas être nul, mais le moi faible peut l'être, et le moi sans propriétaire peut conduire à des pointeurs pendants ... quelque chose d'infâme dans Objective-C. J'espère que cela aide

"UNOWNED Les références faibles et non possédées se comportent de la même façon mais ne sont PAS les mêmes."

Les références non possédées, comme les références faibles, n'augmentent pas le nombre de rétentions de l'objet référé. Cependant, dans Swift, une référence non possédée a l' avantage supplémentaire de ne pas être facultative . Cela les rend plus faciles à gérer plutôt que d'avoir recours à la liaison facultative. Ce n'est pas différent des options implicitement non emballées. De plus, les références non possédées ne sont pas à zéro . Cela signifie que lorsque l'objet est désalloué, il ne remet pas à zéro le pointeur. Cela signifie que l'utilisation de références non possédées peut, dans certains cas, conduire à des pointeurs pendants. Pour les nerds qui se souviennent de l'époque d'Objective-C comme moi, les références non possédées sont mappées aux références unsafe_unretained.

C'est là que ça devient un peu déroutant.

Les références faibles et non possédées n'augmentent pas les décomptes de conservation.

Ils peuvent tous deux être utilisés pour briser les cycles de rétention. Alors, quand les utilisons-nous?!

Selon les documents d' Apple :

«Utilisez une référence faible chaque fois qu'il est valide pour que cette référence devienne nulle à un moment donné au cours de sa durée de vie. Inversement, utilisez une référence non possédée lorsque vous savez que la référence ne sera jamais nulle une fois définie lors de l'initialisation. »

Daksh Gargas
la source
0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

Si vous n'êtes pas sûr, [unowned self] utilisez [weak self]

Shourob Datta
la source