CloseHomeAboutBlogCourse
Bob I’m an iOS instructor/blogger from S.Korea.July 14 • 5 min read • Edit

The Delegate and Callbacks in iOS

Deal with async code. There is no black and white.

Story of Mine

I used to believe the world of programming is a gentle place where code is executed from top to bottom.

If you are already familiar with sync/async code, feel free to get to the main section.

print("Hello, world")
print("Life is great")
print("How are you?")

Not long after, I've discovered we don't live in an ideal world. We may have to execute functions only after another.

This situation often occurs when you work with UI tasks such as pressing a button to network with the server.

func likePost() {
  // increase "likes" by one
}

Let us create an IBAction which is responsible for calling the likePost method above.

class MyViewController: UIViewController {  
  @IBAction func didPressLikeButton(sender: UIButton) {
    likePost() // increase like in database/coredata
  }
}

One thing to note, likePost() is called irregularly whenever the user presses the button. The user can spam it or wait 30 minutes. Let's take a look at the drawing below.

---tab---1s---tab---2s---tab---300s---tab--->

The above also can be also described as,

---tab---other-tasks---tab---other-tasks---tab--->

Analogy: Asynchronous code is like a waiter in a restaurant. When the customer orders (presses the button), the waiter does not have to wait all day from the kitchen. Instead, he/she can do other tasks such as cleaning the dishes and serving other tables (execute other lines of code meanwhile).

Well, you can learn more about async vs sync code in the articles below.

Prerequisite

Before you begin, this article is written for advanced developers who are familiar with closure syntax and how the delegate/data source pattern works. If you are not comfortable with any, feel free to check out the articles below and come back.

Besides, you are aware of memory management with Automatic Referencing Count. If you want to get everything in one spot, feel free to enroll Learn Swift 4 with Bob. The below is just a promo video. Feel free to skip.

Delegate vs Closure

As the title indicates, in iOS programming, there are dominantly two ways to deal with async code. 1. The closure way 2. The delegate way.

Note: There are many other ways including KVO, property observers, notification, sequence, target-action.

In this article, I will discuss the pros and cons and possibly help you decide which pattern to use. However, this article isn't about choosing one just because everyone else, including Apple engineers and other famous people, is using it. Let's build our own perspectives through looking at a variety of cases.

Delegate Way

The early Apple engineers used to love the delegate pattern to handle async tasks. Let's create one.

Note: Again, I'm not going to explain how the delegate pattern works. I've covered it previously.

Design Protocol

First, we will use design a protocol which will be used as a type that UIViewController conforms to.

If you are not comfortable with the previous statement, please do not proceed.

I have used an example from Why you shouldn’t use delegates in Swift by Marin. Thanks for the example. However, the article would have been better if he had presented other examples besides the one below.

protocol NetworkServiceDelegate {
    func didComplete(result: String)
}

Design Delegator/Sender

Let's design a class which is responsible for grabbing data from the server.

class NetworkService {
    var delegate: NetworkServiceDelegate?

    func fetchDataFromUrl(url: String) {
        API.request(.GET, url) { result in
            delegate?.didCompleteRequest(result)
        }
    }
}

The fetchDataFromUrl method will be executed by NetworkService. When the task has been completed, it will call the didCompleteRequest method from the delegate.

Design Delegate/Receiver

Let's set the relationship.

class MyViewController: UIViewController, NetworkServiceDelegate {

    let networkService = NetworkService()

    override func viewDidLoad() {
        super.viewDidLoad()
        networkService.delegate = self
    }

    func didCompleteRequest(result: String) {
        print("I got \(result) from the server!")
    }
}

Now, whenever the user presses the button,

@IBAction func didPressButton(sender: UIButton) {
 networkService.fetchDataFromUrl("http://localhost:4000/user")
}

The didCompleteRequest method is called by the delegator, and you have access to result within MyViewController.

Great, you've learned how to pass data between two objects through the delegate pattern.

Retain Cycle

However, there is a strong reference cycle. First, MYViewController has a property of NetworkService. Second, NetworkService contains a delegate property which has been assigned by MYViewController.

To prevent this from happening, you have to define the delegate as weak and add class to the protocol.

weak var delegate: NetworkServiceDelegate?

and

protocol NetworkServiceDelegate: class {
    func didComplete(result: String)
}

If you are not understanding the statement above, again, feel free to enroll the course and then come back after. This article is written for advanced developers.

Closure Way

Now, let's compare with the closure/completion-handler/callback way.

Design Network Service

Instead of a delegate property and protocol, you only need an optional closure block called, onComplete. This closure block is only executed when you've received a callback from the API.resquest function.

Note: Again, there is a lot going on. If you are not understanding the statement above, this article may not be the right tutorial.

class NetworkService {

    var onComplete: ((result: String)->())?

    func fetchDataFromUrl(url: String) {
        API.request(.GET, url) { result in
            onComplete?(result: result)
        }
    }
}

Design View Controller

Let us create a view controller which owns NetworkService.

class MyViewController: UIViewController {

    let networkService = NetworkService()

    override func viewDidLoad() {
        super.viewDidLoad()
        networkService.onComplete = { result in
            print("I got \(result) from the server!")
        }
    }

}

Let's grab data when the user presses the button.

@IBAction func didPressButton(sender: UIButton) {
 networkService.fetchDataFromUrl("http://localhost:4000/user")
}

The closure way allows you to use the onComplete function instead of creating floating functions within MyViewController. Often times, those delegate functions are organized in a way that you can't follow. Just think about those delegate functions within UITableview to handle the user interaction.

Comparison

Let's compare.

Length and Relationship

The closure way is shorter. When you work with the delegate way, you have to implement the required functions to conform to the protocol.

When you use the closure way, even if the networkService object calls the method, MYViewController can simply ignore. Therefore, the closure way is often called, "decoupled". The relationship is rather weak and fragile.

Some would argue that you can make the delegate function as "optional". But, now you see your code look as below.

@objc protocol MyProtocol {
    @objc optional func doSomething()
}

Aint' good. The closure way wins.

Memory Management

When it comes to memory management, you have to define the protocol as class and define the delegate property as weak to prevent retain cycle.

When you work with the closure way, you have to define self either with unowned or weak. If you are not using self, you don't have to worry about retain cycle.

Not bad. Both wins.

Backward Communication

If you want to communicate back to the NetworkService, using the delegate way, you have to use the datasource pattern by returning shown below.

func didCompleteRequest(result: String) -> String {
  print("Thanks for the data")
}

If you want to communicate back to the networkService object using the closure way, you also have to return something within onComplete.

var onComplete: ((result: String)->(String))? // return `String`
networkService.onComplete = { result in
    return "Thank you for helping, network object!"
}

Well, there isn't much difference. Both wins.

Dryness

If you want to implement many other delegate protocols, you probably have to do something like this,

extension MYViewController: UIViewController, GoogleMapDelegate, GoogleMapDataSource, UITableView, ... {

  let gooleMap = GoogleMap()
  let tableView = UITableView()
  let anotherObject = UIAnotherClass()

  override func viewDidLoad() {
      super.viewDidLoad()
      gooleMap.delegate = self
      tableView.delegate = self
      anotherObject.delegate = self
      }

   }

   func cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {}

   // ...
   // ...
   // loads of other delegate functions 
   // that will make you doomed 
}

The code above looks hideous and you will have a hard time tracking and debugging. Good luck. Yes, we've tried to manage and distribute through extension but it will still be tough my friend.

But when it comes to the closure way, the only thing you need to do is

extension MYViewController {

  let gooleMap = GoogleMaps()
  let tableView = UITableView()
  let anotherObject = UIAnotherClass()

  override func viewDidLoad() {
    super.viewDidLoad()

    googleMap.getCurrentLocation = { location in
      print("Yay, I'm \(location")
    }

  }

}

But you notice the getCurrentLocation is an optional function that you don't need to handle. MYViewController can simply ignore.

The closure way wins.

The example above is contrived and I've made up but I hope you get the point. Google Maps API uses GMSMapViewDelegate to handle async code.

General Opinion

Based on the analysis above, it seems like the closure is winning in most cases. Yes, that's the reason why many other platforms begin to shift from the delegate way to the closure way.

Let's take a look.

What do other platforms prefer?

React Native

Let's take a look at React Native using javascript.

<View>
  <ListView
  dataSource={this.state.dataSource}
  renderRow={(rowData) => <Text>{rowData.title}, {rowData.releaseYear}</Text>}
  />
</View>    

You don't have to understand the code above except one. => is a function/closure in javascript. rowData is the parameter and what comes after is the rest of the closure block.

React and React Native use the closure way.

RxSwift

Let's take a look at RxSwift which I love

buttton.rx.tap
     .debounce(1.0, scheduler: MainScheduler.instance)
     .subscribe(){ event in print("The user pressed the button!") }
     .addDisposableTo(disposeBag)

Again, you don't have to understand the code above. You will notice that after the .subscribe function, there is a callback/completion handler block.

Javascript

Let's take a look at pure javascript

function getMoviesFromApiAsync() {
  return fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {
      return responseJson.movies;
    })
    .catch((error) => {
      console.error(error);
    });
}

Again, you don't have to understand the code above, but it's all about callbacks and closures. In fact, javascript people love callbacks. Their lives are revolved around handling closures. I can speak for them since I'm currently spending most of the day coding in javascript.

What does Swift use?

As I've mentioned earlier, Swift APIs use both the closure and the delegate way. However, there is a slight shift these days. One of the examples is UIAlerView. In the past, there was a delegate protocol called,UIAlertViewDelegate which handles the user action such as opening and closing the AlertView. Now it has been depreciated since iOS 8.

Back in the days, func alertViewCancel(UIAlertView) and func willPresent(UIAlertView) used to exist to handle the user action.

These days, the AlertView API utilities the closure way to notify us developers what kind of action the user has done with the object.

// Initialize Actions
  let yesAction = UIAlertAction(title: "Yes", style: .Default) { (action) -> Void in
      print("The user is okay.")
  }

  let noAction = UIAlertAction(title: "No", style: .Default) { (action) -> Void in
      print("The user is not okay.")
  }

Conclusion

Yes, the world is shifting towards the closure way. However, the delegate pattern can't simply not exist because UIViewController is the delegate of the operating system, just that there is no protocol.

Built-in function such as viewDidLoad, viewDidAppeear are managed by the UIApplication singleton.

Feel free to watch one of the tutorials on Life Cycle of an iOS App on YouTube.

The delegate pattern is something we can't escape if you want an object to be managed by robots/computers, not a human. It applies to front-end development and android development.

You may use either the delegate pattern or closure pattern to handle async tasks. But, you now understand why it is shifting towards the closure way.

More Async

Other libraries exist such as RxSwift, then, Async exist to handle asynchronous code. They provide a way to handle async code. One thing to note is that they all use closures and completion handler approach over the delegate pattern.

Thought on RxSwift

I might consider making a course on RxSwift and posts of them. If you are interested, feel free to get updated here whenever I post an article.

About Me

iOS Developer from South Korea. Feel free to follow my story on Instagram or get serious on LinkedIn