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