简体   繁体   中英

Unowned self in a closure in a closure

If I have a closure in another closure is it enough to use unowned/weak once in the outer closure to avoid retain cycles?

Example:

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

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

Only declaring weak or unowned self in the capture list of the outer closure is enough to avoid retain cycles if you don't create a strong reference to self within the outer closure (eg by doing: guard let strongSelf = self else { return }).

If you do create a strong reference within the closure, you must add a capture list to the inner closure to ensure that it captures your strong reference to self weakly.

Here are some examples:

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

Yes, however I would use weak over unowned because self.doStuff() with throw an exception if nil while if you use weak and its self?.doStuff() there won't be an exception thrown and it just won't execute.

You can test this in the playground with the following code:

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?()

and compare it with:

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?()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM