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

Nir*_*rma 11 closures weak-references automatic-ref-counting swift unowned-references

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

 someFunctionWithTrailingClosure { [weak self] in
     anotherFunctionWithTrailingClosure { [weak self] in 
         self?.doSomething()
     }
 }
Run Code Online (Sandbox Code Playgroud)

如果我[weak self]someFunctionWithTrailingClosure自己的捕获列表中声明自己没有重新声明它weak再次在捕获列表中anotherFunctionWithTrailingClosure self已经成为一种Optional类型但是它也成为一个weak参考?

谢谢!

Rob*_*Rob 16

[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")
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

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) 
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你这样做,你会看到doSomething永远不会调用它,deinit并在anotherFunctionWithTrailingClosure调用它的闭包之前调用它.

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


Sen*_*ful 6

TL; 博士

尽管[weak self]外部块中使用一次就可以了(EX1),但如果您将此引用更改为 strong(例如guard let self = self),则您还需要[weak self]在内部块中使用a (EX3)。

同样[weak self]内部块上只使用一次通常是一个错误(EX2_B)。不幸的是,这是重构代码时常犯的错误,并且在创建时很难发现。


一个好的经验法则是,weak如果对象在闭包外立即变强,则始终使用。

不保留的示例self(即通常这些是“好”场景):

// EX1
fn { [weak self] in
  self?.foo() 
}
Run Code Online (Sandbox Code Playgroud)
// EX2
fn { [weak self] in 
  fn2 {
    self?.foo()
  }
}
// self is weak inside fn, thus adding an extra `[weak self]` inside fn2 is unnecessary
Run Code Online (Sandbox Code Playgroud)
// EX3
fn { [weak self] in 
  guard let self = self else { return }
  fn2 { [weak self] in
    self?.foo()
  }
}
Run Code Online (Sandbox Code Playgroud)

确实保留的示例self(即通常是“坏”场景):

// EX1_B
fn {
  self.foo()
}
// fn retains self
Run Code Online (Sandbox Code Playgroud)
// EX2_B
fn {
  fn2 { [weak self] in
    self.foo()
  }
}
// fn retains self (this is a common, hard-to-spot mistake)
Run Code Online (Sandbox Code Playgroud)
// EX3_B
fn { [weak self] in 
  guard let self = self else { return }
  fn2 {
    self.foo()
  }
}
// fn2 retains self
Run Code Online (Sandbox Code Playgroud)

正如Hamish 所暗示的,有两个主要原因weak是有用的:

  1. 以防止保留循环。
  2. 防止对象的寿命超过应有的寿命。

更多关于#2(防止长寿命对象)

Rob 的示例中,该函数没有保留闭包(除了 dispatch_async 之外,它几乎可以保证在将来的某个时刻触发闭包),因此您永远不会以保留周期结束。因此,weak在这种情况下使用是为了防止 #2 发生。

正如 Hamish 所提到的,在这个例子中实际上并不需要弱来防止保留循环,因为没有保留循环。weak在这种情况下,用于防止对象存活时间超过所需时间。这完全取决于您的用例,当您考虑一个对象的寿命超过所需时间时。因此,有时您只想使用weak外部(EX2),有时您想使用weak外部,strong内部,weak内部舞蹈(EX3)。

更多关于#1(防止保留循环)

为了检查保留循环问题,假设一个函数正在存储对块(即堆)的引用,而不是直接引用该函数(即堆栈)。很多时候,我们不知道一个类/函数的内部,所以它的安全假设功能保留块。

现在您可以轻松地使用weak外部创建一个保留循环,并且只使用strong内部(EX3_B):

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
*/

Run Code Online (Sandbox Code Playgroud)

请注意,deinit它没有被调用,因为创建了一个保留循环。

这可以通过删除strong引用(EX2)来解决:

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

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

public func testCompletionHandlers() {
    someFunctionWithTrailingClosure { [weak self] in
        guard let self = self else { return }
        self.anotherFunctionWithTrailingClosure { [weak self] in
            self?.doSomething()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)