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

一笑奈何 提交于 2019-12-18 02:43:16

问题


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!


回答1:


The [weak self] in anotherFunctionWithTrailingClosure is not needed.

You can empirically test this:

class Experiment {

    func someFunctionWithTrailingClosure(closure: () -> ()) {
        print("starting someFunctionWithTrailingClosure")
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
            closure()
            print("finishing someFunctionWithTrailingClosure")
        }
    }

    func anotherFunctionWithTrailingClosure(closure: () -> ()) {
        print("starting anotherFunctionWithTrailingClosure")
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
            closure()
            print("finishing anotherFunctionWithTrailingClosure")
        }
    }

    func doSomething() {
        print("doSomething")
    }

    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() {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) { 
        let obj = Experiment()

        obj.testCompletionHandlers()

        // sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler

        NSThread.sleepForTimeInterval(1.5) 
    }
}

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

Having verified this behavior, I'd personally still be inclined to use the [weak self] syntax on anotherFunctionWithTrailingClosure to make my intent explicit, as all of this is far from obvious.




回答2:


Updated for Swift 4.2:

public class CaptureListExperiment {

    public init() {

    }

    func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting someFunctionWithTrailingClosure")

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            closure()
            print("finishing someFunctionWithTrailingClosure")
        }
    }

    func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting anotherFunctionWithTrailingClosure")

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            closure()
            print("finishing anotherFunctionWithTrailingClosure")
        }
    }

    func doSomething() {
        print("doSomething")
    }

    public func testCompletionHandlers() {
        someFunctionWithTrailingClosure { [weak self] in
            guard let self = self else { return }
            self.anotherFunctionWithTrailingClosure { // [weak self] in
                self.doSomething()
            }
        }
    }

    // go ahead and add `deinit`, so I can see when this is deallocated

    deinit {
        print("deinit")
    }
}

try it Playgorund:

func performExperiment() {

    let obj = CaptureListExperiment()

    obj.testCompletionHandlers()
    Thread.sleep(forTimeInterval: 1.3)
}

performExperiment()


来源:https://stackoverflow.com/questions/38739129/do-capture-lists-of-inner-closures-need-to-redeclare-self-as-weak-or-unowne

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!