Delegates in Swift

Async operations

Many times you want to perform tasks that take time and that cannot be finished right away, such as loading an image from a server or requesting data from an API (“Application Programming Interface”). Because Swift is awesome, we can pass functions as parameters so we can call them as soon as we’re done performing a certain task. In those cases you could use completion-blocks, such as this one:

func loadImage(from url: URL, completion: (image: UIImage?, error: Error?) -> ()) {
    // Do networking here...

    // When finished, call the completion handler and pass the
    // requested parameters
    completion(image, error)
}

As soon as the code within the function was performed and completion: is called, the code you entered when calling that function is executed. This could for example look something like this:

imageLoader.loadImage(from: puppyImageUrl) { (image, error) in
    guard let data = data, error == nil else {
        // Handle errors here
        return
    }

    // Do something with image
}

Introducing delegates

This works, and it works well but the downside to that approach is that you have to take care of the result right there and now. Oftentimes you want to update your app’s UI, as soon as this time-intensive task of let’s say loading an image is complete. So to solve that we create a delegate that we can then attach to our UI-handler, which gets notified as soon as the task is complete.

If we wanted to update the function above to make use of a delegate instead of a completion handler, we first need to create the delegate itself. A delegate is a protocol, so it only holds method stubs and no actual code. Here’s an example.

protocol ImageLoaderDelegate: class {
    func didLoad(image: UIImage, from url: URL)
    func failedToLoadImage(from url: URL, with error: Error)
}

We can then hold a weak reference to it in our ImageLoader like this:

class ImageLoader {
    weak var delegate: ImageLoaderDelegate?

    //...
}

If we just leave it like this, obviously nothing will happen, because we’re not notifying the delegate of any results. So let’s update our function to the following:

func loadImage(from url: URL) {
    // Do networking here

    self.delegate?.didLoad(image: image, from: url)
    
    // Of if the call failed, call the other delegate function
    
    self.delegate?.failedToLoadImage(from: url, with: error)
}

You can then assign the delegate to the object which handles UI updates, for example some UIViewController. When assigning the delegate using imageLoader.delegate = self, Xcode will complain because the ViewController does not conform to the protocol ImageLoaderDelegate. To fix that error, we can simply create an extension to the UI handler where we conform to the delegate, for example like this:

extension ViewController: ImageLoaderDelegate {
    func didLoad(image: UIImage, from url: URL) {
        // Update UI here
    }

    func failedToLoadImage(from url: URL, with error: Error) {
        // Handle errors here
    }
}

I don’t want all those methods!

Sweet! ViewController now gets notified of any delegate calls that imageLoader makes. But what if we don’t want to have to conform to every function in the delegate because you want different viewcontrollers to do different things with the same imageLoader instance?
Do achieve this optionality, we have to mark our delegate with @objc and the functions with @objc optional. So our delegate now looks like this:

@objc protocol ImageLoaderDelegate: class {
    @objc optional func didLoad(image: UIImage, from url: URL)
    @objc optional func failedToLoadImage(from url: URL, with error: Error)
}

We could then have ViewController conform to ImageLoaderDelegate without implementing any of its methods. This is useful if we want to our data-handling object to provide additional functionality without requiring every class which conforms to its protocoll to implement all the method. If you want to read more about delegates, take a look at the official documentation

That’s it for this article, I hope you learned something 🙂 If you have any questions, suggestions or complaints, you can comment below or send me an email.
Thank you for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *