内部闭包的捕获列表是否需要将“self”重新声明为“weak”或“unowned”?
Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?
如果我将闭包传递给这样的函数:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
如果我在 someFunctionWithTrailingClosure
的捕获列表中将自己声明为 [weak self]
而没有在 anotherFunctionWithTrailingClosure
self
的捕获列表中再次将其重新声明为 weak
已经成为 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
上使用 [weak self]
语法来明确我的意图。
已针对 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()
TL;DR
尽管在 outer 块中使用一次 [weak self]
没问题(EX1),但如果将此引用更改为强引用(例如 guard let self = self
),您在内部块中也需要一个 [weak self]
(EX3)。
同时在 内部 块上仅使用一次 [weak self]
通常是一个错误 (EX2_B)。不幸的是,这是重构代码时常犯的错误,并且在发生时很难被发现。
一个好的经验法则是,如果对象在闭包外立即很强,则始终使用 weak
。
不保留 self
的示例(即通常是 "good" 场景):
// 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
的示例(即通常 "bad" 场景):
// 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
与一样,weak
有用的主要原因有两个:
- 防止保留循环。
- 为了防止对象的寿命超过它们应有的寿命。
关于 #2 的更多信息(防止长寿命对象)
在 中,该函数不保留闭包(在 dispatch_async 之后几乎可以保证在将来的某个时候触发闭包),因此您永远不会结束带有一个保留周期。所以在这种情况下使用 weak
是为了防止 #2 发生。
正如 Hamish 所提到的,在此示例中实际上不需要 weak 来防止保留循环,因为没有保留循环。 weak
,在这种情况下,用于防止对象活得比需要的时间长。这完全取决于您的用例,何时考虑一个对象比需要的寿命更长。因此,有时您只想在外部(EX2)使用 weak
,而其他时候您想要使用 weak
外部、strong
内部、weak
例如内心之舞 (EX3)。
关于#1 的更多信息(防止循环保留)
为了检查保留循环问题,假设一个函数正在存储对块(即堆)的引用,而不是直接引用函数(即堆栈)。很多时候我们不知道 class/function 的内部结构,所以更安全的假设是函数 是 保留块。
现在您可以使用 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()
}
}
}
或者使用weak/strong/weak舞蹈(EX3):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
}
如果我将闭包传递给这样的函数:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
如果我在 someFunctionWithTrailingClosure
的捕获列表中将自己声明为 [weak self]
而没有在 anotherFunctionWithTrailingClosure
self
的捕获列表中再次将其重新声明为 weak
已经成为 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
上使用 [weak self]
语法来明确我的意图。
已针对 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()
TL;DR
尽管在 outer 块中使用一次 [weak self]
没问题(EX1),但如果将此引用更改为强引用(例如 guard let self = self
),您在内部块中也需要一个 [weak self]
(EX3)。
同时在 内部 块上仅使用一次 [weak self]
通常是一个错误 (EX2_B)。不幸的是,这是重构代码时常犯的错误,并且在发生时很难被发现。
一个好的经验法则是,如果对象在闭包外立即很强,则始终使用 weak
。
不保留 self
的示例(即通常是 "good" 场景):
// 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
的示例(即通常 "bad" 场景):
// 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
与weak
有用的主要原因有两个:
- 防止保留循环。
- 为了防止对象的寿命超过它们应有的寿命。
关于 #2 的更多信息(防止长寿命对象)
在 weak
是为了防止 #2 发生。
正如 Hamish 所提到的,在此示例中实际上不需要 weak 来防止保留循环,因为没有保留循环。 weak
,在这种情况下,用于防止对象活得比需要的时间长。这完全取决于您的用例,何时考虑一个对象比需要的寿命更长。因此,有时您只想在外部(EX2)使用 weak
,而其他时候您想要使用 weak
外部、strong
内部、weak
例如内心之舞 (EX3)。
关于#1 的更多信息(防止循环保留)
为了检查保留循环问题,假设一个函数正在存储对块(即堆)的引用,而不是直接引用函数(即堆栈)。很多时候我们不知道 class/function 的内部结构,所以更安全的假设是函数 是 保留块。
现在您可以使用 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()
}
}
}
或者使用weak/strong/weak舞蹈(EX3):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
}