all.herranz
all.herranz

Reputation: 153

Break Swift closure retain circle with not weak explicit capture

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

Answers (2)

rob mayoff
rob mayoff

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:

diagram showing both the instance of ImageLoader and the completion closure pointing at the instance of Cache

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:

diagram showing the completion closure pointing at the instance of ImageLoader, and the instance of ImageLoader pointing at the instance of Cache

Upvotes: 3

Shadowrun
Shadowrun

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

Related Questions