Better Completion Handlers In Swift

So it has been almost a week since iOS Dev UK in Aberystwyth. The last session I attended was Power Swift which was run by Natasha the Robot. I have been looking at writing better Completion Handlers in Swift.

Within the session Natasha helped us through a series of Playground files on: ‘Error Handling’, ‘Higher Order Functions’, ‘Protocol Orientated Programming’, and ‘Reference Types vs. Value Types’. One thing that struck me is the approach to completion handlers. An approach which pushes beyond merely copying the Objective-C way of doing things and embraces the Swift language and brings out of the Swift Architect within you.

A pattern you see all of the time in Objective-C is the use of completion handlers. Code like the following is common place within an iOS application written in Objective-C:

- (void)fetch:(NSString *)contentId withCompletionHandler(void (^)(NSDictionary *response, NSError *error))block {
  
  ...
  if (httpResponse.statusCode != 200) {
    NSError *error = [[NSError alloc] initWithDomain:[NSString stringWithFormat:@"%@", [httpResponse URL]] code: 500 userInfo: nil];
    block(nil, error);
  } else {
      ...
      block(dictionary, nil);
  }
}

This code defines a method ‘fetch‘ and within it if an error occurs it creates a new ‘NSError‘ object and sends the error back in the completion handler. If the call is successful a dictionary object is sent back in the completion handler. The calling code will typically look like the following:

[contentStore fetch:contentId withCompletionHandler:^(NSDictionary *result, NSError *error){
  if (error) {
    // An error occurred
  } else {
    Content *content = [[Content alloc] initWithDictionary:result];
  }
}];

Looking at the return values of the completion handler we can check whether an error occurred and handle appropriately.

When I wrote my first apps in Swift I copied the way I had always coded apps in Objective-C. This resulted in the standard Objective-C completion handler pattern being translated into Swift as is. For brevity I have kept the example very simple and not included any surrounding code:

public class ContentStore {

  public func fetch(completionHandler: (AnyObject?, NSError?) -> Void) {
    if error {
      completionHandler(nil, NSError(domain: "com.example", code: 1000, userInfo: nil))
    } else {
      ...
      completionHandler(dictionary, nil)
    }
  }
  
}

Then within the calling code you end up with something which looks like the following:

ContentStore().fetch { (result, error) -> Void in
  if let unWrappedError = error {
    print("\(unWrappedError)")
  } else {
    if let unwrappedResult = result {
      print("\(unwrappedResult)")
    }
  }
}

There’s another way

Swift presents us with an alternative to this pattern. A pattern which is more expressive and improves understanding of the code. Swift introduced enumerations. We can use enumerations to define whether a call was a ‘Success’ or ‘Failure’.

An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.

class ContentStore {

  typealias CompletionHandlerType = (Result) -> Void
  
  enum Result {
    case Success(AnyObject?)
    case Failure(Error)
  }
  
  enum Error: ErrorType {
    case AuthenticationFailure
  }
  
  func fetch(completionHandler: CompletionHandlerType) {
    let random = Int(arc4random_uniform(7))
    if (random > 4) {
      completionHandler(Result.Success(1))
    } else {
      completionHandler(Result.Failure(Error.AuthenticationFailure))
    }
  }
  
}

The completion handler is defined within a typealias. The completion handler returns the enumeration ‘Result‘. There are two enumeration values ‘Success’ or ‘Failure’. The ‘Success’ value takes an ‘AnyObject’ value and the success an enumeration ‘Error’ which is of type ‘ErrorType’ The ‘fetch’ function defines a random number and uses this value to either return a ‘Success’ or ‘Failure’, ‘Success’ if the random number if greater than 4.

ContentStore().fetch { (result) -> Void in
  switch (result) {
  case .Success(let number):
    if let unwrappedNumber = number {
        print("\(unwrappedNumber)")
    }
    break;
  case .Failure(let error):
    print("\(error)")
    break;
  }
}

The use of an enumeration within the completion handler allows us to use a switch statement to respond to ‘Success’ or ‘Failure’. Once you are within the case either ‘Success’ or ‘Failure’ you can read the passed value.

Embracing the language features of Swift can lead to elegant solutions which enhance understanding. I’m having fun exploring this pattern and find it easier to understand than the equivalent in Objective-C.

2 replies
  1. Josh Vickery
    Josh Vickery says:

    Thanks for this writeup! I had been struggling to find a pattern that I was happy with, and this really helps with readability.

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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