分类：Swift

The delegate pattern is okay. But there’s a better way.

You’re building an iOS app. You have your nice, SOLID architecture in place. You have a model, a networking layer, a UI layer, and maybe some helpers in between. The textbook way you would pass data around those layers would be trough delegation, a really useful and common pattern in iOS development.

Delegation simply means that you pass yourself to someone else, when you want that someone to notify you of changes, so you can react to them. For instance, if a ViewController talks to a network service, and wants to get notified when that service is done with some request, it would make itself the network service’s delegate. The network service would then call the delegate methods when it’s done.

Using callbacks for delegation

Callbacks are similar in function to the delegate pattern. They do the same thing: letting other objects know when something happened, and passing data around.

What differentiates them from the delegate pattern, is that instead of passing a reference to yourself, you are passing a function. Functions are first class citizens in Swift, so there’s no reason why you wouldn’t have aproperty that is a function!

MyClass now has a myFunction property that it can call and anyone can set (since properties are internal by default in Swift). This is the basic idea of using callbacks instead of delegation. Here’s the same example as before but with callbacks instead of a delegate:

So why are callbacks better?

1. Decoupling

Delegates lead to pretty decoupled code. It doesn’t matter to theNetworkService who its delegate is, as long as they implement the protocol. However, the delegate has to implement the protocol, and if you’re using Swift instead of @objc protocols, the delegate has to implement every method in the protocol. (since there’s no optional protocol conformance)

When using callbacks, on the other hand, the NetworkService doesn’t even need to have a delegate object to call methods on, nor does it knowanything about who’s implementing those methods. All it cares about is when to call those methods. Also, not all of the methods need to be implemented.

2. Multiple delegation

What if you want to notify a ViewController when a request finishes, but maybe also a some sort of logger class, and some sort of analytics class.

With delegates, you would have to have an array of delegates, or three different delegate properties that might even have different protocols! (I’ll be the first to admit I’ve done this)

With callbacks, however, you could define an array of functions (I love Swift) and call each of those when something’s done. There’s no need to have a bunch of different objects and protocols risking retain cycles and writing boilerplate code.

3. Clearer separation of concerns

The way I see the difference between delegates and callbacks is that with delegates, the NetworkService is telling the delegate “Hey, I’ve changed.” With callbacks, the delegate is observing the NetworkService.

In reality, the difference is minimal, but thinking in the latter way helps prevent anti-patterns often found with delegation, like making the NetworkService transform results for presentation, which should not be its job!

4. Easier testing!

Ever felt like your codebase is twice as big with unit tests, because you have to mock every protocol, including all of the delegates in your app?

With callbacks, not only do you not have to mock any delegates, but it lets use use whatever callback you want in each test!

In one test, you might test if the callback gets called, then in another you might test if it’s called with the right results. And none of those require a complicated mocked delegate with someFuncDidGetCalled booleans and similar properties.

I personally think callbacks lead to clearer code and tests, and are a muchSwiftier (and better) way to pass data around in your app. I hope you’ve learnt something new today, and good luck out there!