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)
        }
    }
}

2 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.

Leave a Comment