[英]Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?
如果我有一个闭包传递给这样的函数:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
如果我宣布自己的[weak self]
在someFunctionWithTrailingClosure
的捕获列表没有重新声明它是weak
在捕获列表中再次anotherFunctionWithTrailingClosure
self
已经成为一种Optional
类型,但它也成为一个weak
引用呢?
谢谢!
不需要anotherFunctionWithTrailingClosure
的[weak self]
。
您可以凭经验对此进行测试:
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")
}
}
然后:
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)
}
}
如果你这样做,你会看到doSomething
永远不会被调用,并且deinit
在anotherFunctionWithTrailingClosure
调用它的闭包之前anotherFunctionWithTrailingClosure
调用。
话虽如此,我可能仍然倾向于在anotherFunctionWithTrailingClosure
上使用[weak self]
语法来明确我的意图。
TL; 博士
尽管在外部块中使用一次[weak self]
(EX1),但如果您将此引用更改为 strong(例如, guard let self = self
),则内部块中也需要一个[weak self]
(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
有用的主要原因有两个:
在Rob 的示例中,该函数没有保留闭包(除了 dispatch_async 之外,它几乎可以保证在将来的某个时刻触发闭包),因此您永远不会以保留周期结束。 因此,在这种情况下使用weak
是为了防止 #2 发生。
正如 Hamish 所提到的,在这个例子中实际上并不需要弱来防止保留循环,因为没有保留循环。 在这种情况下, weak
用于防止对象存活时间超过所需时间。 这完全取决于您的用例,当您考虑一个对象的寿命超过所需时间时。 因此,有些时候你会想使用weak
只能在外面(EX2),以及其他时候,你会想用weak
外, strong
内心, weak
内舞(EX3)为例。
为了检查保留循环问题,假设一个函数正在存储对块(即堆)的引用,而不是直接引用该函数(即堆栈)。 很多时候,我们不知道一个类/函数的内部,所以它的安全假设功能保留块。
现在您可以使用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()
}
}
}
为 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")
}
}
试试 Playgorund:
func performExperiment() {
let obj = CaptureListExperiment()
obj.testCompletionHandlers()
Thread.sleep(forTimeInterval: 1.3)
}
performExperiment()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.