swiftclosuresautomatic-ref-countingweak-referencesunowned-references

Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?


If I have a closure passed to a function like this:

 someFunctionWithTrailingClosure { [weak self] in
     anotherFunctionWithTrailingClosure { [weak self] in 
         self?.doSomething()
     }
 }

If I declare self as [weak self] in someFunctionWithTrailingClosure's capture list without redeclaring it as weak again in the capture list of anotherFunctionWithTrailingClosure self is already becoming an Optional type but is it also becoming a weak reference as well?

Thanks!


Solution

  • The [weak self] in anotherFunctionWithTrailingClosure is not needed.

    You can empirically test this:

    class Experiment {
        func someFunctionWithTrailingClosure(closure: @escaping () -> Void) {
            print("starting", #function)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                closure()
                print("finishing", #function)
            }
        }
    
        func anotherFunctionWithTrailingClosure(closure: @escaping () -> Void) {
            print("starting", #function)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                closure()
                print("finishing", #function)
            }
        }
    
        func doSomething() {
            print(#function)
        }
    
        func testCompletionHandlers() {
            someFunctionWithTrailingClosure { [weak self] in
                self?.anotherFunctionWithTrailingClosure { // [weak self] in
                    self?.doSomething()
                }
            }
        }
    
        // go ahead and add `deinit`, so I can see when this is deallocated
    
        deinit {
            print("deinit")
        }
    }
    

    And then:

    func performExperiment() {
        DispatchQueue.global().async {
            let obj = Experiment()
    
            obj.testCompletionHandlers()
    
            // sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler
    
            Thread.sleep(forTimeInterval: 1.5)
        }
    }
    

    If you do this, you will see that doSomething is never called and that deinit is called before anotherFunctionWithTrailingClosure calls its closure.

    That having been said, I might still be inclined to use the [weak self] syntax on anotherFunctionWithTrailingClosure to make my intent explicit.