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



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

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

如果我在someFunctionWithTrailingClosure的捕获列表中将自己声明为[weak self],而在anotherFunctionWithTrailingClosure的捕获列表中不将其重新声明为weak,那么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从未被调用,并且deinitanotherFunctionWithTrailingClosure调用其闭包之前被调用。

话虽如此,我可能仍然倾向于在anotherFunctionWithTrailingClosure上使用[weak self]语法来明确我的意图。

TL;博士

尽管在外部块中使用[weak self]一次是可以的(EX1),但如果将此引用更改为强(例如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有用的主要原因有两个:

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

请注意,由于创建了保留循环,因此未调用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()

相关内容

  • 没有找到相关文章

最新更新