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()
}
// 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)
// EX3_B
fn { [weak self] in
guard let self = self else { return }
fn2 {
self.foo()
}
}
// fn2 retains self
正如Hamish 所暗示的,有两个主要原因weak
是有用的:
- 防止保留循环。
- 以防止对象的寿命超过应有的寿命。
更多关于#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
*/
请注意,deinit
它没有被调用,因为创建了一个保留循环。
这可以通过删除strong
参考(EX2)来解决:
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
//guard let self = self else { return }
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}
或者使用弱/强/弱的舞蹈(EX3):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
}