Many asynchronous Objective-C APIs pass you two optional values in their completion handler: one for the method’s result if the operation was a success, and an error value in case the operation failed.

An example is the CLGeocoder.reverseGeocodeLocation method in the Core Location framework. It takes a CLLocation object and sends the coordinates to a web service to turn them into a readable address. When the network request completes, the method calls its completion block with an optional array of CLPlacemark objects and an optional Error value:

class CLGeocoder { ... func reverseGeocodeLocation ( _ location : CLLocation , completionHandler : @escaping ([ CLPlacemark ]?, Error ?) -> Void ) ... }

This pattern of returning a pair of optional success value and optional error is the most practical way to handle this kind of situation in an Objective-C API. If this were a Swift API with no requirement to be callable from Objective-C, you would design it differently, though.

Two possible outcomes, four potential states

The problem with the current API is that the operation really has only two possible outcomes: either the request succeeds and returns a result, or it fails and returns an error. However, the code as it is allows four different states:

Result is non- nil and error is nil . Error is non- nil and result is nil . Both are non- nil . Both are nil .

The documentation of the API can take care to explicitly preclude the last two cases, but as a user you can never really be sure that the documentation is correct.

A better design using Result

In Swift you might design the same API like this:

class CLGeocoder { ... func reverseGeocode ( location : CLLocation , completion : @escaping ( Result < [ CLPlacemark ] > ) -> Void ) ... }

The completion block now only receives one (non-optional) argument, which has the type Result<…> . Result is an enum that’s very similar to Swift’s Optional type. The only difference is that it can also store an error value in the failure case, whereas Optional only has an associated value for its success case:

enum Result < T > { case success ( T ) case failure ( Error ) }

Result is currently not part of the Swift standard library, but it will probably be added at some time. Until then, it’s trivial to define it yourself, or you can use the popular antitypical/Result library.

With this fictional new API, the compiler could guarantee that the argument that gets passed to the completion block can only ever have two states, success or failure. You wouldn’t have to worry about the possibility that both values are present or both absent.

An initializer to turn (T?, Error?) into Result<T>

However, we can’t change Apple’s APIs, so there’s nothing we can do about the inherent ambiguity in the completion block’s arguments. What we can do is to contain the logic how to convert an optional success value and an optional error into a single Result value in one place. I do this in my code with a convenience initializer for Result :

import Foundation // needed for NSError extension Result { /// Initializes a Result from an optional success value /// and an optional error. Useful for converting return /// values from many asynchronous Apple APIs to Result. init ( value : T ?, error : Error ?) { switch ( value , error ) { case ( let v ?, _ ): // Ignore error if value is non-nil self = . success ( v ) case ( nil , let e ?): self = . failure ( e ) case ( nil , nil ): let error = NSError ( domain : "ResultErrorDomain" , code : 1 , userInfo : [ NSLocalizedDescriptionKey : "Invalid input: value and error were both nil." ]) self = . failure ( error ) } } }

In the case where both inputs are nil (which normally should never happen) this creates a custom error to put into the result. I use NSError for this, but you could use any type that conforms to the Error protocol.

Having defined this initializer, I use the geocoding API like this:

let location = ... let geocoder = CLGeocoder () geocoder . reverseGeocodeLocation ( location ) { placemarks , error in // Turn arguments into Result let result = Result ( value : placemarks , error : error ) // Only work with result from here switch result { case . success ( let p ): ... case . failure ( let e ): ... } }