break Swift 闭包 retain circle with not weak explicit capture

Break Swift closure retain circle with not weak explicit capture

下面的 Swift by Sundell 文章指出,在某些情况下,在 class 中显式捕获 属性 足以打破引用保留循环。这是确切的例子:

class ImageLoader {
    private let cache = Cache<URL, Image>()

    func loadImage(
        from url: URL,
        then handler: @escaping (Result<Image, Error>) -> Void
    ) {
        // Here we capture our image loader's cache without
        // capturing 'self', and without having to deal with
        // any optionals or weak references:
        request(url) { [cache] result in
            do {
                let image = try result.decodedAsImage()
                cache.insert(image, forKey: url)
                handler(.success(image))
            } catch {
                handler(.failure(error))
            }
        }
    }
}

这样您就可以避免后验空检查以提高可读性。结合起来会很有用,就像下面解释的那样 article

我不明白为什么这会破坏保留循环,因为 [缓存] 捕获仍然是对 ImageLoader 的强引用 属性

[cache] 捕获仍然是对 ImageLoader 属性 的强引用,但不保留自身。这延长了缓存对象的生命周期,同时该请求(url)回调块持有对缓存的强引用 - self 甚至可以在回调块完成之前被释放,并且缓存可以稍微停留一下更长。

如果存在强引用循环 A->B 和 B->A,或者 A->B->C 和 C->A 等,您只会得到一个循环引用。这里我们有 A->缓存和 A 创建但随后移交给 url 会话的某些块保留了缓存。所以这不是循环 A->Cache and request(url) completion handler also -> Cache。 A 可以自由释放,这意味着缓存引用计数将从 2 变为 1,并且在 url 会话下载期间仍然存在。

cache 属性 引用 Cache 的实例,它是与 ImageLoader 的实例分开的对象。对于您在问题中发布的代码,参考如下所示:

假设我们实现 loadImage 而没有将 cache 放入捕获列表:

class ImageLoader {
    private let cache = Cache<URL, Image>()

    func loadImage(
        from url: URL,
        then handler: @escaping (Result<Image, Error>) -> Void
    ) {
        request(url) { result in
                    // ^^^^^^^^^ no capture list
            do {
                let image = try result.decodedAsImage()
                self.cache.insert(image, forKey: url)
             // ^^^^^ we have to explicitly reference self instead
                handler(.success(image))
            } catch {
                handler(.failure(error))
            }
        }
    }
}

然后引用看起来像这样: