Reputation: 153
The following Swift by Sundell article points out that in some cases is enough to make a explicit capture of a property inside the class to break the references retain cycle. This is the exact example:
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))
}
}
}
}
This way you could avoid posterior null checks to improve readability. It would be usefull with combine like it is explained in the following article
I don't understand why this breaks the retain circle as [cache] capture is still a strong reference to the ImageLoader property
Upvotes: 0
Views: 233
Reputation: 385700
The cache
property refers to an instance of Cache
, which is a separate object from the instance of ImageLoader
. For the code you posted in your question, the references look like this:
Suppose instead we implement loadImage
without putting cache
in the capture list:
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))
}
}
}
}
Then the references look like this:
Upvotes: 3
Reputation: 3857
[cache] capture is still a strong reference to the ImageLoader property but doesn't retain self. And that prolongs the lifetime of just the cache object while a strong reference to cache is held by that request(url) callback block - self can be dealloced even before the callback block is done, and cache can hang around a bit longer.
You only get a retain cycle if there is a loop of strong references A->B and B->A, or A->B->C and C->A etc. Here we have A->Cache and some block that A created but then hands off to url session retains Cache. so that's not a cycle A->Cache and request(url) completion handler also -> Cache. A is free to be dealloced, meaning Cache reference count would go from 2 to 1 and still be around while the url session downloads.
Upvotes: 3