Using Swift Throws with Completion Callbacks
By: . Published: . Categories: swift async.Swift 2 introduced the notion of throwing and propagating NSError
values.
It works pretty well in a linear, synchronous workflow, but at first glance, it doesn’t appear to address the common case of completion callbacks.
Consider NSURLSession.dataTaskWithURL(_:completionHandler:)
.
Swift 2 bridges this in like so:
func dataTaskWithURL(url: NSURL,
completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)
-> NSURLSessionDataTask?
Note how, in the completion handler closure, you still have to do Ye Olde Check Data Then Check Error dance. Yawn.
There’s a straightforward way to transform this into throws
-land, though.
Just think: What sort of thing can throw? A function call.
So, let’s use our functions, and rewrite this to:
typealias DataTaskResult = () throws -> (NSData, NSURLResponse)
func dataTaskWithURL(url: NSURL,
completionHandler: DataTaskResult -> Void)
-> NSURLSessionDataTask?
The completion handler is not marked as @rethrows
, so it has to handle any
error. Extracting the result or error is then done in the completion handler
like so:
{ result: DataTaskResult in
do {
let data, response = try result()
/* work with data and response */
} catch {
/* you got yourself an error! */
}
}
This straightforward transformation preserves Swift 2’s
directing attitude
towards error-handling,
while freeing users from having to remember the protocol for working with
NSError
s.
It’s unfortunate we can’t ourselves apply this to Apple’s code.
We’ll just have to continue to type through the
error-prone, manual procedure for working with NSError
s when working
with their APIs.
We needn’t continue to do so with our own, though:
if you’re going to adopt throws
, go whole-hog, and throw
ify your entire
API, both synchronous and asynchronous.