捕获内部闭包列表是否需要将“self”重新声明为“weak”或“unowned”?

时间:2016-08-03 09:06:14

标签: swift closures automatic-ref-counting weak-references unowned-references

如果我将一个闭包传递给这样的函数:

 someFunctionWithTrailingClosure { [weak self] in

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

 }

如果我在[weak self]的捕获列表中将自己声明为someFunctionWithTrailingClosure而未在weak anotherFunctionWithTrailingClosure的捕获列表中再次将其重新声明为self成为Optional类型,但它也成为weak引用?

谢谢!

3 个答案:

答案 0 :(得分:13)

不需要[weak self]中的anotherFunctionWithTrailingClosure

你可以凭经验测试这个:

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")
    }
}

然后:

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) 
    }
}

如果你这样做,你会发现永远不会调用doSomething,并且在deinit调用其关闭之前调用anotherFunctionWithTrailingClosure

在验证了这种行为之后,我个人仍然倾向于使用[weak self]上的anotherFunctionWithTrailingClosure语法来明确我的意图,因为所有这些都不是很明显。

答案 1 :(得分:2)

已为Swift 4.2更新:

Pacing

尝试Playgorund:

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")
    }
}

答案 2 :(得分:2)

TL; DR

尽管在 outer 块中一次使用[weak self]是可以的(EX1),但如果将此引用更改为“ strong”(例如guard let self = self),则需要一个{ {1}}也位于内部代码块(EX3)。

inner 块上也仅使用一次[weak self]通常是错误(EX2_B)。不幸的是,这是重构代码时常犯的错误,并且在编写时很难发现。


一个好的经验法则是,如果对象在闭包外部紧紧,则始终使用[weak self]

不保留weak的示例(即通常是“好”方案):

self
// EX1
fn { [weak self] in
  self?.foo() 
}
// EX2
fn { [weak self] in 
  fn2 {
    self?.foo()
  }
}
// self is weak inside fn, thus adding an extra `[weak self]` inside fn2 is unnecessary

确实保留// EX3 fn { [weak self] in guard let self = self else { return } fn2 { [weak self] in self?.foo() } } 的示例(即通常是“不良”情况):

self
// EX1_B
fn {
  self.foo()
}
// fn retains self
// EX2_B
fn {
  fn2 { [weak self] in
    self.foo()
  }
}
// fn retains self (this is a common, hard-to-spot mistake)

Hamish alludes to一样,// EX3_B fn { [weak self] in guard let self = self else { return } fn2 { self.foo() } } // fn2 retains self 有用的主要原因有两个:

  1. 为了防止保留周期。
  2. 防止物体的寿命超过其应有的寿命。

有关#2(防止寿命长的物体)的更多信息

Rob's example中,该函数未保留闭包(在dispatch_async之外,但保证将来会在某个时候触发闭包),因此您永远不会以保留周期结束。因此,在这种情况下使用weak是为了防止#2发生。

如Hamish所提到的,由于没有保留周期,因此在本示例中实际上不需要弱以防止保留周期。在这种情况下,weak用于防止对象的生存时间超过所需的时间。当您考虑某个对象的寿命超过所需时间时,这完全取决于您的用例。因此,有时您只想在外部(EX2)上使用weak,而有时又想使用weak外部,weak内部,strong内部舞曲(EX3)。

关于#1的更多信息(防止保留周期)

要检查保留周期问题,假设一个函数正在存储对该块(即堆)的引用,而不是直接引用该函数(即堆栈)。很多时候我们都不知道类/函数的内部,因此更安全地假设函数 保留了该块。

现在,您可以使用外部weak并仅使用内部weak(EX3_B)轻松创建保留周期:

strong

请注意,由于创建了保留周期,因此未调用public class CaptureListExperiment { public init() { } var _someFunctionWithTrailingClosure: (() -> ())? var _anotherFunctionWithTrailingClosure: (() -> ())? func someFunctionWithTrailingClosure(closure: @escaping () -> ()) { print("starting someFunctionWithTrailingClosure") _someFunctionWithTrailingClosure = closure DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in self?._someFunctionWithTrailingClosure!() print("finishing someFunctionWithTrailingClosure") } } func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) { print("starting anotherFunctionWithTrailingClosure") _anotherFunctionWithTrailingClosure = closure DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in self?._anotherFunctionWithTrailingClosure!() 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") } } func performExperiment() { let obj = CaptureListExperiment() obj.testCompletionHandlers() Thread.sleep(forTimeInterval: 1.3) } performExperiment() /* Output: starting someFunctionWithTrailingClosure starting anotherFunctionWithTrailingClosure finishing someFunctionWithTrailingClosure doSomething finishing anotherFunctionWithTrailingClosure */

可以通过删除deinit引用(EX2)来解决此问题:

strong

或使用弱/强/弱舞蹈(EX3):

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