Friday, March 23, 2018

Dealing With Weak in Closure-based Delegation

Oleg Dreyman (via Joshua Emmons):

Let’s look at the core of the problem: 99% of the time, when assigning a delegation callback, there should be a [weak self] capture list, but nothing is actually preventing ourselves from omitting it. No errors, no warnings, nothing. What if instead we could force the correct behavior?

[…]

Leveraging the power of Swift generics, we can do better:

struct DelegatedCall<Input> {

    private(set) var callback: ((Input) -> Void)?
    
    mutating func delegate<Object : AnyObject>(to object: Object, with callback: @escaping (Object, Input) -> Void) {
        self.callback = { [weak object] input in
            guard let object = object else {
                return
            }
            callback(object, input)
        }
    }
}

Update (2025-10-30): Nick Main:

I’ve used keypaths in our codebase to help prevent problems with forgetting to use [weak self] in closures.

Instead of a closure we take an object+keypath pair and the object is held weakly. Accessing a computed property via keypath is like a zero-arg method in these use cases.

Full method keypaths will make this pattern capable of replacing many more closure use cases for us.

3 Comments RSS · Twitter


Dreyman's general thesis is solid:

Previously [in the classic Cocoa delegate pattern], the designer of an ImageDownloader API was responsible for not introducing any memory leaks to our app. Now [with closure-based delegation], it’s an exclusive obligation of the API user.

However, I disagree with his taste that this kind of declaration

 init() {
        downloader.didDownload.delegate(to: self) { (self, image) in
            self.image = image
        }
    }

is more developer-friendly than the original

    init() {
        downloader.didDownload = { [weak self] image in
            self.image = image
        }
    }

Despite my distaste for Swift's disorganized “junk after the opening brace” closure syntax, the capture list still seems a bit cleaner compared to the extra method call and shadowing of `self` as an additional parameter.


@Ben His way is definitely less clean. The question is whether it’s worth it to prevent errors.


LOL, so we went full circle all the way from object and selector to delegate protocols to blocks to … objects and selectors (function “keypaths”). Swift is really making us march forward.

Leave a Comment