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))
}
}
}
}
然后引用看起来像这样:
下面的 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))
}
}
}
}
然后引用看起来像这样: