Delegate Strength Tip: No More Weakness

Now here’s a simple tip worthy of some signal boosting: If you had to pick the most annoying thing about modern Cocoa programming, that continues to regularly trip up even the best of the experts, what would you pick?

Yep, the weak-strong dance, that was our pick too. From the original @weakify and @strongify horrors through the currently conventional [weak-self]-guard-return boilerplate in Swift, it’s just distracting noise and all to easy to overlook with no help from the compiler in catching your retain cycles isn’t it?

Well, here is by far the most elegant solution to that problem we’ve ever seen!

Do you often forget [weak self]? Here’s a solution

You see, from an API design point of view, this new approach actually made things worse. Previously, the designer of an ImageDownloader API was responsible for not introducing any memory leaks to our app. Now, it’s an exclusive obligation of the API user.

And [weak self] is something that is incredibly easy to overlook. I’m more than sure that there is a lot of code out there (even in production) that suffers from this simple, but so ubiquitous problem … We can’t rely even on ourselves to write [weak self] in every part of our app (where needed), and we certainly cannot expect all our potential API users to do the same. As API designers, we need to care about safety at the point of use.

Exactly so!

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?

Yes, that is, in fact, something we do want to force. And Messr. Dreyman has a remarkably concise and elegant solution to adopt:

public struct Delegated<Input, Output> {

private(set) var callback: ((Input) -> Output?)?

public mutating func delegate<Target: AnyObject>(to target: Target,
with callback: @escaping (Target, Input) -> Output) {
self.callback = { [weak target] input in
guard let target = target else { return nil }
return callback(target, input)

and then you use like

self.downloader = ImageDownloader()
downloader.didDownload.delegate(to: self) { (self, image) in
self.currentImage = image

That’s a massive leap in safety for very little work, we’re going to adopt it immediately.

With handy accessors and conveniences to go along the library’s still under 50 lines of code — copy the code (or load it with your package manager of choice) from Delegated on github!

Alex | March 14, 2018

Leave a Reply