2016-06-15 2 views
2

Si j'ai une fermeture dans une autre fermeture est-ce suffisant d'utiliser une seule fois/faible une fois dans la fermeture extérieure pour éviter les cycles de retenue?Soi sans moi dans une fermeture dans une fermeture

Exemple:

foo.aClosure({[unowned self] (allowed: Bool) in 
      if allowed { 
       self.doStuff() 

       self.something.anotherClosure({ (s:String) -> (Void) in 
        self.doSomethingElse(s) 
       }) 
      } 
     }) 
+0

Que voulez-vous accomplir? – kandelvijaya

Répondre

0

Oui, mais j'utiliser faible sur unowned parce self.doStuff() avec une exception si jeter nulle en .doStuff() si vous utilisez la faiblesse et de son auto, il a gagné? t être une exception lancée et elle ne s'exécutera pas.

Vous pouvez le tester dans la cour de récréation avec le code suivant:

typealias Closure =() -> Void 

class ClosureObject { 
    var closure:Closure? 
    func saveClosure(closure:Closure?) { 
     self.closure = closure 
    } 
} 

let mainClosureObject = ClosureObject() 

class TestObject { 
    let closureObject = ClosureObject() 
    func log() { 
     print("logged") 
    } 
    func run() { 
     mainClosureObject.saveClosure() {[weak self] in 
      self?.closureObject.saveClosure() { 
       self?.log() 
      } 
     } 
    } 
} 

var testObject:TestObject? = TestObject() 
let closureObject = testObject?.closureObject 
testObject?.run() 
mainClosureObject.closure?() 
closureObject?.closure?() 
testObject = nil 
closureObject?.closure?() 
mainClosureObject.closure?() 
closureObject?.closure?() 

et le comparer avec:

typealias Closure =() -> Void 

class ClosureObject { 
    var closure:Closure? 
    func saveClosure(closure:Closure?) { 
     self.closure = closure 
    } 
} 

let mainClosureObject = ClosureObject() 

class TestObject { 
    let closureObject = ClosureObject() 
    func log() { 
     print("logged") 
    } 
    func run() { 
     mainClosureObject.saveClosure() { 
      self.closureObject.saveClosure() { 
       self.log() 
      } 
     } 
    } 
} 

var testObject:TestObject? = TestObject() 
let closureObject = testObject?.closureObject 
testObject?.run() 
mainClosureObject.closure?() 
closureObject?.closure?() 
testObject = nil 
closureObject?.closure?() 
mainClosureObject.closure?() 
closureObject?.closure?() 
1

seulement déclarant auto faible ou unowned dans la liste de capture de la fermeture extérieure est assez pour éviter les cycles de retenue si vous ne créez pas une forte référence à soi dans la fermeture externe (par exemple en faisant: guard let strongSelf = self else {return}).

Si vous créez une référence forte dans la fermeture, vous devez ajouter une liste de capture à la fermeture interne pour vous assurer qu'elle capture votre forte référence à elle-même faiblement.

Voici quelques exemples:

import Foundation 
import PlaygroundSupport 

class SomeObject { 
    typealias OptionalOuterClosure = ((Int) -> Void)? 
    typealias InnerClosure =() -> Void 

    var outerClosure: OptionalOuterClosure 

    func setup() { 
     // Here are several examples of the outer closure that you can easily switch out below 
     // All of these outer closures contain inner closures that need references to self 

     // optionalChecks 
     // - has a capture list in the outer closure 
     // - uses the safe navigation operator (?) to ensure that self isn't nil 
     // this closure does NOT retain self, so you should not see the #2 calls below 
     let optionalChecks: OptionalOuterClosure = { [weak self] callNumber in 
      print("outerClosure \(callNumber)") 

      self?.delayCaller { [weak self] in 
       print("innerClosure \(callNumber)") 
       self?.doSomething(callNumber: callNumber) 
      } 
     } 

     // copiedSelfWithInnerCaptureList 
     // - has a capture list in the outer closure 
     // - creates a copy of self in the outer closure called strongSelf to ensure that self isn't nil 
     // - has a capture list in the inner closure 
     // - uses the safe navigation operator (?) to ensure strongSelf isn't nil 
     // this closure does NOT retain self, so you should not see the #2 calls below 
     let copiedSelfWithInnerCaptureList: OptionalOuterClosure = { [weak self] callNumber in 
      guard let strongSelf = self else { return } 
      print("outerClosure \(callNumber)") 

      strongSelf.delayCaller { [weak strongSelf] in 
       print("innerClosure \(callNumber)") 
       strongSelf?.doSomething(callNumber: callNumber) 
      } 
     } 

     // copiedSelfWithoutInnerCaptureList 
     // - has a capture list in the outer closure 
     // - creates a copy of self in the outer closure called strongSelf to ensure that self isn't nil 
     // - does NOT have a capture list in the inner closure and does NOT use safe navigation operator 
     // this closure DOES retain self, so you should see the doSomething #2 call below 
     let copiedSelfWithoutInnerCaptureList: OptionalOuterClosure = { [weak self] callNumber in 
      guard let strongSelf = self else { return } 
      print("outerClosure \(callNumber)") 

      strongSelf.delayCaller { 
       print("innerClosure \(callNumber)") 
       strongSelf.doSomething(callNumber: callNumber) 
      } 
     } 

     // retainingOuterClosure 
     // - does NOT have any capture lists 
     // this closure DOES retain self, so you should see the doSomething #2 call below 
     let retainingOuterClosure: OptionalOuterClosure = { callNumber in 
      print("outerClosure \(callNumber)") 

      self.delayCaller { 
       print("innerClosure \(callNumber)") 
       self.doSomething(callNumber: callNumber) 
      } 
     } 

     // Modify which outerClosure you would like to test here 
     outerClosure = copiedSelfWithInnerCaptureList 
    } 

    func doSomething(callNumber: Int) { 
     print("doSomething \(callNumber)") 
    } 

    func delayCaller(closure: @escaping InnerClosure) { 
     delay(seconds: 1, closure: closure) 
    } 

    deinit { 
     print("deinit") 
    } 
} 

// Handy delay method copied from: http://alisoftware.github.io/swift/closures/2016/07/25/closure-capture-1/ 
func delay(seconds: Int, closure: @escaping() -> Void) { 
    let time = DispatchTime.now() + .seconds(seconds) 
    DispatchQueue.main.asyncAfter(deadline: time) { 
     print("") 
     closure() 
    } 
} 

var someObject: SomeObject? = SomeObject() 
someObject?.setup() 

// Keep a reference to the outer closure so we can later test if it retained someObject 
let copiedOuterClosure = someObject!.outerClosure! 

// Call the outer closure once just to make sure it works 
copiedOuterClosure(1) 

// Wait a second before we destroy someObject to give the first call a chance to work 
delay(seconds: 1) { 
    // Run the outerClosure again to check if we retained someObject 
    copiedOuterClosure(2) 

    // Get rid of our reference to someObject before the inner closure runs 
    print("de-referencing someObject") 
    someObject = nil 
} 

// Keep the main run loop going so our async task can complete (need this due to how playgrounds work) 
PlaygroundPage.current.needsIndefiniteExecution = true 
+0

Êtes-vous sûr de copiedSelfWithoutInnerCaptureList? J'ai vérifié seulement celui-ci et ai obtenu "deinit", ainsi il n'y avait aucune retenue. – eilas

+0

Comme mentionné dans les commentaires ci-dessus chaque variante 'outerClosure', le test n'est pas de savoir si 'deinit' apparaît, mais si les '# 2 calls' apparaissent ou non. Donc, copiedSelfWithoutInnerCaptureList dit, "cette fermeture ne se retient pas, alors vous devriez voir l'appel doSomething # 2 ci-dessous." Cela est dû au fait que innerClosure (qui crée les appels # 2) n'est effectué que si l'objet SomeObject est conservé. – jblack