在swift中嵌套闭包中正确放置捕获列表

Bra*_*ott 14 closures nested capture ios 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.
        */
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,[weak self]在两个尾随闭包中使用,但是如果我[weak self]从任何一个尾随闭包中省略,编译器都非常高兴.

因此,我有3个选项来定义我的捕获列表:

  1. 定义每个嵌套闭包上的捕获,直到引用
  2. 仅在第一个闭包上定义捕获.
  3. 仅在实际使用引用的最嵌套闭包上定义捕获.

我的问题是:

如果我知道我ExampleDataSource可以nil在某个时候,最好的选择是什么?

Bra*_*ott 16

值得注意的是,GCD dispatch_async不会导致保留周期.换句话说,当块完成执行时,GCD将不保留块内的任何引用.

对于类之间的强引用或分配给实例属性的闭包内的强引用,情况也是如此. Apple文档

话虽如此,在这个例子中,正确答案是选项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")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

首先我创造,var myDataSource: ExampleDataSource? = ExampleDataSource().

然后我跑了myDataSource.loadContent().

在完成处理程序有机会运行之前,我设置myDataSource = nil,删除对它的所有引用.

调试控制台指示未保留对self的引用:

init()
loadContent()
deinit
loadedContent
Run Code Online (Sandbox Code Playgroud)

看起来我们找到了答案!但为了完成,让我们测试替代方案......

如果[weak self]仅在最内部的尾随闭包上捕获,则GCD将保留,ExampleDataSource直到块完成执行,这解释了为什么调试将如下所示:

init()
loadContent()
loadedContent
deinit
Run Code Online (Sandbox Code Playgroud)

如果没有包含捕获列表会发生同样的事情,我们永远不会自行解包self,尽管编译器会尝试警告你!

虽然[weak self]在所有尾随闭包中包含捕获在技术上并不正确,但它确实减损了代码的可读性,并且感觉不像"类似Swift".