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

Let’s talk about closure-based delegation, retain cycles and generics

Oleg Dreyman
AnySuggestion
6 min readMar 11, 2018

--

Read a 2020 follow-up to this story here: No more [weak self], or the weird new future of delegation

Okay, so today’s post is about delegation and how we can make it better with Swift. Without further ado, let’s introduce a standard example of Cocoa-style delegation in Swift:

  1. First, we create a delegate protocol restricted to classes
protocol ImageDownloaderDelegate: class {    func imageDownloader(_ imageDownloader: ImageDownloader, didDownload image: UIImage)}

2. Next, we implement our ImageDownloader

class ImageDownloader {

weak var delegate: ImageDownloaderDelegate?

func downloadImage(for url: URL) {
download(url: url) { image in
self.delegate?.imageDownloader(self, didDownload: image)
}
}

}

Note how delegate is marked as weak to prevent retain cycles. If you’re reading this post, I suggest you already know why we need this, but if you don’t, make sure to check out this brilliant article from NatashaTheRobot: iOS: How To Make Weak Delegates In Swift.

3. And then let’s implement a user for ImageDownloader

class Controller {

let downloader = ImageDownloader()
var image: UIImage?

init() {
downloader.delegate = self
}

func updateImage() {
downloader.downloadImage(for: /* some image url */)
}

}
extension Controller: ImageDownloaderDelegate { func imageDownloader(_ imageDownloader: ImageDownloader, didDownload image: UIImage) {
self.image = image
}
}

This works brilliantly. We have a clean, familiar API, and we don’t need to worry about memory leaks since our delegate is weak. To be clear — this example is totally functional and completely fine. There’s nothing wrong with it.

So what’s the point of this post?

Modern Swift

Well, today this Cocoa-style delegation pattern is getting less and less popular amongst Swift developers. The reasons are clear: the code doesn’t look very “modern”, it requires a substantial amount of boilerplate and it lacks flexibility (just try to write a delegate like this for something that is generic, for example).

That’s why more and more developers opt in for a “delegation through closures” pattern. Let’s apply it to our ImageDownloader example:

First, we get rid of ImageDownloaderDelegate protocol as well as the delegate in our ImageDownloader. Instead, we add a simple closure property

class ImageDownloader {

var didDownload: ((UIImage) -> Void)?

func downloadImage(for url: URL) {
download(url: url) { image in
self.didDownload?(image)
}
}

}

And we modify our Controller accordingly

class Controller {

let downloader = ImageDownloader()
var image: UIImage?

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

func updateImage() {
downloader.downloadImage(for: /* some image url */)
}

}

Our code is now much more compact and (presumably) easier to read and reason about. However, an experienced developer will notice an issue right away: we have a memory leak!

While getting rid of our delegate property in ImageDownloader, we also lost its weak behavior. So right now we have a Controller which holds a reference to an ImageDownloader which holds a reference to a Controller through its didDownload closure. It’s the classic definition of a retain cycle and an associated memory leak with it.

An experienced developer will also know an easy solution to this problem: “just use [weak self]”!

class Controller {

let downloader = ImageDownloader()
var image: UIImage?

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

func updateImage() {
downloader.downloadImage(for: /* some image url */)
}

}

And now it works just fine again.

Buuuuuut…

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.

Swift API Design Guidelines famously states:

Entities such as methods and properties are declared only once but used repeatedly.

And that’s important. 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.

And Swift, of course, allows us to come up with a better approach.

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?

Here’s what I’m talking about, in the most basic way:

class ImageDownloader {

private var didDownload: ((UIImage) -> Void)?

func setDidDownload<Object : AnyObject>(delegate: Object, callback: @escaping (Object, UIImage) -> Void) {
self.didDownload = { [weak delegate] image in
if let delegate = delegate {
callback(delegate, image)
}
}
}

func downloadImage(for url: URL) {
download(url: url) { image in
self.didDownload?(image)
}
}

}

So now our didDownload is private, and instead the user should call setDidDownload(delegate:callback:), which wraps the passed delegate object as a weak reference, thus enforcing the behavior we want. Here’s how it will look for Controller:

class Controller {

let downloader = ImageDownloader()
var image: UIImage?

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

func updateImage() {
downloader.downloadImage(for: /* some image url */)
}

}

And boom, no retain cycles and no memory leaks — fantastic! Our code is still compact and readable, and it’s much more safe when it comes to memory leaks. And no ugly [weak self] as well!

Note: this smart technique is actually also used by Foundation’s UndoManager (and some other Cocoa APIs as well).

One step further

But this is also not ideal. We need to write a lot of extra code in our ImageDownloader, and it’s not very reusable. Leveraging the power of Swift generics, we can do better:

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

}

Hence, the boilerplate is gone and we’re back to having a very thin API

class ImageDownloader {

var didDownload = Delegated<UIImage>()

func downloadImage(for url: URL) {
download(url: url) { image in
self.didDownload.callback?(image)
}
}

}

And the code in Controller looks even nicer (from my point of view)

class Controller {

let downloader = ImageDownloader()
var image: UIImage?

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

func updateImage() {
downloader.downloadImage(for: /* some image url */)
}

}

So now we have just 14 lines of code that can potentially save us from lots of unintentional memory leaks.

This is what I love about Swift and about its type system. It challenges me to fight common pitfalls with creative design. The Delegated API speaks for itself, allowing us to write clean, expressive and safe code.

This technique combines the best of Cocoa-style delegation and closure-based delegation while solving most of their problems. I’ve been using it a lot, so I’ve decided to publish it as a package. It has a more general syntax and is called Delegated. Make sure to take a look:

Thanks for reading the post! Don’t hesitate to ask or suggest anything in the “responses” section below. You can also contact me on Twitter or find me on GitHub. If you have written a piece (or stumbled upon one) exploring similar topic — make sure to post a link to it in the responses so I can include it right below.

Further learning:

Hi! I’m Oleg, the author of Women’s Football 2017 and an independent iOS/watchOS developer with a huge passion for Swift. While I’m in the process of delivering my next app, you can check out my latest project called “The Cleaning App” — a small utility that will help you track your cleaning routines. For business inquiries, I can be reached at oleg@dreyman.dev. Thanks for your support!

--

--

Oleg Dreyman
AnySuggestion

iOS development know-it-all. Talk to me about Swift, coffee, photography & motorsports