在 swift 的嵌套闭包中正确放置捕获列表
Correct placement of capture list in nested closures in swift
我在哪里为 Swift 中的嵌套闭包定义捕获引用?
以这段代码为例:
import Foundation
class ExampleDataSource {
var content: Any?
func loadContent() {
ContentLoader.loadContentFromSource() { [weak self] loadedContent in
// completion handler called on background thread
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.content = loadedContent
}
}
}
}
class ContentLoader {
class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
/*
Load content from web asynchronously,
and call completion handler on background thread.
*/
}
}
在此示例中,[weak self]
用于两个尾随闭包,但是如果我从任一尾随闭包中省略 [weak self]
,编译器将非常满意。
所以我有 3 个选项来定义我的捕获列表:
- 在指向引用的每个嵌套闭包上定义捕获
- 仅在第一个闭包上定义捕获。
- 仅在实际使用引用的嵌套最多的闭包上定义捕获。
我的问题是:
If I know that my ExampleDataSource
could be nil
at some point, what is the best option to go with?
重要的是要注意 GCD dispatch_async 不会导致保留循环。换句话说,当块完成执行时,GCD 将不会保留块内的任何引用。
对于 类 之间的强引用或分配给实例的 属性 的闭包内的强引用,情况并非如此。 Apple Documentation
也就是说,在此示例中,正确答案是选项 2,即仅在第一个闭包上定义捕获。
出于测试目的,我稍微修改了代码:
class ExampleDataSource {
init() {
print("init()")
}
deinit {
print("deinit")
}
var content: Any?
func loadContent() {
print("loadContent()")
ContentLoader.loadContentFromSource() { [weak self] loadedContent in
dispatch_async(dispatch_get_main_queue()) {
print("loadedContent")
self?.content = loadedContent
}
}
}
}
class ContentLoader {
class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
sleep(5) // thread will hang for 5 seconds
completion(loadedContent: "some data")
}
}
}
首先我创建,var myDataSource: ExampleDataSource? = ExampleDataSource()
。
那我运行myDataSource.loadContent()
.
在完成处理程序有机会 运行 之前,我设置了 myDataSource = nil
,删除了对它的所有引用。
调试控制台表明未保留对自身的引用:
init()
loadContent()
deinit
loadedContent
看来我们找到了答案!但为了完成起见,让我们测试替代方案...
如果 [weak self]
仅在最内部的尾随闭包上捕获,GCD 将保留 ExampleDataSource
直到该块执行完毕,这解释了为什么调试看起来像这样:
init()
loadContent()
loadedContent
deinit
如果不包含捕获列表并且我们从未有选择地解包 self
,同样的事情也会发生,尽管编译器会尝试警告您!
虽然在所有尾随闭包中包含 [weak self]
捕获在技术上并不是不正确的,但它确实降低了代码的可读性并且感觉不太 'Swift-like'。
我在哪里为 Swift 中的嵌套闭包定义捕获引用?
以这段代码为例:
import Foundation
class ExampleDataSource {
var content: Any?
func loadContent() {
ContentLoader.loadContentFromSource() { [weak self] loadedContent in
// completion handler called on background thread
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.content = loadedContent
}
}
}
}
class ContentLoader {
class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
/*
Load content from web asynchronously,
and call completion handler on background thread.
*/
}
}
在此示例中,[weak self]
用于两个尾随闭包,但是如果我从任一尾随闭包中省略 [weak self]
,编译器将非常满意。
所以我有 3 个选项来定义我的捕获列表:
- 在指向引用的每个嵌套闭包上定义捕获
- 仅在第一个闭包上定义捕获。
- 仅在实际使用引用的嵌套最多的闭包上定义捕获。
我的问题是:
If I know that my
ExampleDataSource
could benil
at some point, what is the best option to go with?
重要的是要注意 GCD dispatch_async 不会导致保留循环。换句话说,当块完成执行时,GCD 将不会保留块内的任何引用。
对于 类 之间的强引用或分配给实例的 属性 的闭包内的强引用,情况并非如此。 Apple Documentation
也就是说,在此示例中,正确答案是选项 2,即仅在第一个闭包上定义捕获。
出于测试目的,我稍微修改了代码:
class ExampleDataSource {
init() {
print("init()")
}
deinit {
print("deinit")
}
var content: Any?
func loadContent() {
print("loadContent()")
ContentLoader.loadContentFromSource() { [weak self] loadedContent in
dispatch_async(dispatch_get_main_queue()) {
print("loadedContent")
self?.content = loadedContent
}
}
}
}
class ContentLoader {
class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
sleep(5) // thread will hang for 5 seconds
completion(loadedContent: "some data")
}
}
}
首先我创建,var myDataSource: ExampleDataSource? = ExampleDataSource()
。
那我运行myDataSource.loadContent()
.
在完成处理程序有机会 运行 之前,我设置了 myDataSource = nil
,删除了对它的所有引用。
调试控制台表明未保留对自身的引用:
init()
loadContent()
deinit
loadedContent
看来我们找到了答案!但为了完成起见,让我们测试替代方案...
如果 [weak self]
仅在最内部的尾随闭包上捕获,GCD 将保留 ExampleDataSource
直到该块执行完毕,这解释了为什么调试看起来像这样:
init()
loadContent()
loadedContent
deinit
如果不包含捕获列表并且我们从未有选择地解包 self
,同样的事情也会发生,尽管编译器会尝试警告您!
虽然在所有尾随闭包中包含 [weak self]
捕获在技术上并不是不正确的,但它确实降低了代码的可读性并且感觉不太 'Swift-like'。