Swift Optional Protocol Methods

After writing about Swift Delegates last week I had a good question asking how to check for optional delegate methods in Swift without using respondsToSelector? If you are coming to Swift from Objective-C this can seem like a big loss. There is a quick answer but it leads to a bigger question…

Optional Protocol Methods

As a brief recap this was the simple protocol we looked at last week to tell a delegate that we had updated a task in our detail view controller:

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

Let’s change this protocol to include an optional method that asks the delegate if we should allow updates to the task:

@objc protocol DetailViewControllerDelegate: AnyObject {
  optional func shouldUpdateTask(sender: DetailViewController) -> Bool
  func didUpdateTask(sender: DetailViewController)
}

Note we need to add the @objc attribute to use an optional method in a Swift protocol. As mentioned last week this also means that a Swift struct or enum can no longer adopt this protocol (no loss in this case as we already made this a class protocol).

Optional Chaining

With the optional method added to the protocol how do we call it? With Objective-C we rely on respondsToSelector: to test if the delegate has implemented the optional method:

if ([self.delegate respondsToSelector:@selector(shouldUpdateTask:)]) {
  BOOL shouldUpdate = [self.delegate shouldUpdateTask:self];
  if (shouldUpdate) {
    // do something
  }
}

With Swift it’s much easier as we can use optional chaining to test for the optional method:

delegate?.shouldUpdateTask?(self)

Note that we first test for the optional delegate (delegate?) and then for the optional delegate method (shouldUpdateTask?). This works well when combined with a guard statement:

guard let shouldUpdate = delegate?.shouldUpdateTask?(self) 
  where shouldUpdate else {
  return
}

// do something

Alternatives to @objc

This approach works but creating a protocol in Swift using the @objc attribute does not seem Swift like. So what are the alternatives when we think we need an optional method in a Swift protocol?

Move the optional methods to a separate protocol

Coming from the Objective-C world it is common to see a long list of optional methods in a protocol definition. Think UITableViewDataSource or UISearchBarDelegate. This great post by Ash Furrow on Protocols and Swift includes a look at how splitting UITableViewDataSource into a number of more logical (non-optional) protocols can better separate concerns.

This might be overkill in our case but we could move the optional method to a separate protocol:

protocol DetailViewControllerTaskUpdatingDelegate: AnyObject {
  func shouldUpdateTask(sender: DetailViewController) -> Bool
}

We can then optionally decide to adopt and implement this new delegate method in our master view controller.

controller.delegate = self
// Uncomment if we need to implement shouldUpdateTask:
// controller.taskUpdatingDelegate = self

Provide a default implementation

Another approach is to provide a default implementation for the “optional” method in an extension. Let’s clean up the protocol definition by removing the @objc and optional attributes:

protocol DetailViewControllerDelegate: AnyObject {
  func shouldUpdateTask(sender: DetailViewController) -> Bool
  func didUpdateTask(sender: DetailViewController)
}

Our method is now mandatory and must be implemented somewhere. If we expect that in most implementations our delegate will always return the same value (e.g. true) we can provide a default implementation in an extension:

extension DetailViewControllerDelegate {
  func shouldUpdateTask(sender: DetailViewController) -> Bool {
    return true
  }
}

Our delegate now only needs to implement the method if it wants to override this default behaviour.

Rethink the problem

Why do we need this optional behaviour in the first place? Maybe the master view controller which owns the task should be telling the detail view controller if it can update the task when first presenting it? This is an artificial example so it is hard to reason too far but if you find yourself fighting the framework or language it may be time to take a step back and rethink.

Further Reading