Quick Guide to Swift Delegates

The delegation design pattern has long been a common technique for macOS and iOS developers. The idea is that a delegating object keeps a reference to another object - the delegate. The delegating object can then inform its delegate when certain events have happened or are about to happen allowing the delegate to update its appearance or state.

It’s such a common pattern, you can’t avoid it if you’re using AppKit or UIKit, it’s worth spending the time to master. Here’s my quick guide to implementing delegates with Swift compared to Objective-C.

Updated 30 Apr 2018 to use AnyObject. See Class Only Protocols In Swift 4

Key Steps to Delegation

The basic steps to use delegation are the same for both Objective-C and Swift:

  1. Create a delegate protocol that defines the messages sent to the delegate.
  2. Create a delegate property in the delegating class to keep track of the delegate.
  3. Adopt and implement the delegate protocol in the delegate class.
  4. Call the delegate from the delegating object.

Note that there are a couple of differences between Swift and Objective-C support for protocols:

  • In Swift you can use protocols with classes, structs or enums. In Objective-C you are limited to classes.
  • Objective-C supports optional protocol methods. Swift only allows optional protocol requirements if you mark the protocol with the @objc attribute. If you use @objc you can then only use the protocol with class types.

Create the Delegate Protocol

Let’s create a delegate protocol that a child or detail view controller might use to tell a parent or master view controller that a task has finished.

In Objective-C:

@protocol DetailViewControllerProtocol <NSObject>
- (void)didFinishTask:(DetailViewController *)sender;
@end

It’s common, but not required, to include the sender in the delegate methods. The Swift version is similar:

protocol DetailViewControllerDelegate: AnyObject {
  func didFinishTask(sender: DetailViewController)
}

The AnyObject keyword in the Swift protocol definition limits protocol adoption to class (object) types and not structures or enums. This is important if we want to use a weak reference to the delegate as will see next.

You may still see this written using class instead of AnyObject:

// Swift 3
protocol DetailViewControllerDelegate: class {
  func didFinishTask(sender: DetailViewController)
}

The use of AnyObject is preferred over class since swift 4. See Class Only Protocols In Swift 4 for details.

Add a Delegate Property

Our delegating class needs a reference to the delegate - but should it be a strong or weak reference? We need to pay attention not to create a retain cycle between the delegate and the delegating objects. That can happen if our delegate already has a strong reference to our delegating object. In that case we should use a weak reference in the delegating object.

In this example, it’s the detail view controller that is delegating so it gets the delegate property. In Objective-C we do that as follows:

@property (weak) id<DetailViewControllerProtocol>delegate;

Since we do not always set a delegate we use an optional property in Swift:

weak var delegate: DetailViewControllerDelegate?

Notes:

  • The use of a weak delegate in Swift is only allowed when we have a class protocol. Xcode will complain if you try to use a weak reference to a non-class protocol since structures and enums use value not reference semantics.
  • Using an optional type for the delegate in Swift also means it is automatically set to nil during initialization.

Adopt and Implement the Delegate Protocol

In our delegate class we tell the compiler that we are adopting the protocol and then implement the delegate method(s). In Objective-C we can use a category to declare the protocol conformance:

@interface MasterViewController () <DetailViewControllerDelegateProtocol>
@end

- (void)didFinishTask:(DetailViewController *)sender {
  // do stuff like updating the UI
}

For the Swift equivalent code I like to use an extension to the master view controller class to declare and implement the delegate function:

extension MasterViewController: DetailViewControllerDelegate {
  func didFinishTask(sender: DetailViewController) {
    // do stuff like updating the UI
  }
}

We also need to remember to set the delegate somewhere. This might be in a prepareForSegue or target-action method of the parent/master view controller that will present the detail view controller:

// Objective-C
detailViewController.delegate = self;
// Swift
detailViewController.delegate = self

Calling Delegate Methods

Finally we can call the delegate from our delegating class. In Objective-C we avoid checking the delegate is set since sending a message to nil does no harm. (If you have optional methods use respondsToSelector: to first test if it is implemented by the delegate):

[self.delegate didFinishTask:self];

In Swift we can use optional chaining to achieve a similar effect. Only if the delegate is not nil do we call the delegate method:

delegate?.didFinishTask(sender: self)

Further Reading

The Apple documentation on delegation is now “retired” but I still find it useful:

There are a number of alternatives to delegate protocols: