Completion Handlers in Swift

来源:互联网 发布:openstack 创建网络 编辑:程序博客网 时间:2024/05/16 18:25

来源:https://grokswift.com/completion-handlers-in-swift/

We’ve been making lots of API calls using Alamofire and dataTaskWithRequest, like in Simple REST API Calls with Swift. And we keep using completion handlers but we’ve never really looked at them carefully to figure out just what they’re doing. Let’s sort that out today.

To make quick and dirty async URL requests in Swift we use this function:

public func dataTaskWithRequest(request: NSURLRequest,   completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)  -> NSURLSessionDataTask

If you’re not familiar with it, there’s lots about it in Simple REST API Calls with Swift. In short, it makes a URL request and, once it gets a response, lets us run code to handle whatever it got back: possibly data, an NSURLResponse, and/or an error. The completion handler is the code that we get to provide to get called when it comes back with those items. It’s where we can work with the results of the call: error checking, saving the data locally, updating the UI, whatever.

Simple API Call

Here’s how we use a simple URL request to get the first post from JSONPlaceholder, a dummy API that happens to have post objects:

First, set up the URL request:

let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"guard let url = NSURL(string: postEndpoint) else {  print("Error: cannot create URL")  return}let urlRequest = NSURLRequest(URL: url)

The guard statement lets us check that the URL we’ve provided is valid.

Then we need an NSURLSession to use to send the request:

let config = NSURLSessionConfiguration.defaultSessionConfiguration()let session = NSURLSession(configuration: config)

Then create the data task:

let task = session.dataTaskWithRequest(urlRequest, completionHandler: nil)

And finally send it (yes, this is an oddly named function):

task.resume()

So that’s how to make the request. How do we do anything with the results?

What’s a Completion Handler?

Completion handlers can be a bit confusing the first time you run in to them. On the one hand they’re a variable or argument but on the other hand they’re a chunk of code. Weird if you’re not used to that kind of thing (a.k.a., blocks or closures).

Completion handlers are super convenient when your app is doing something that might take a little while, like making an API call, and you need to do something when that task is done, like updating the UI to show the data from the API call. You’ll see completion handlers in Apple’s APIs like dataTaskWithRequest and they can be pretty handy in your own code.

In dataTaskWithRequest the completion handler argument has a signature like this:

completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void

So it’s a code block (it must be if it has a return type which is what -> tells us). It has 3 arguments: (NSData?, NSURLResponse?, NSError?) and returns nothing: Void. To specify a completion handler we can write the code block right inline like this:

let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in  // this is where the completion handler code goes})task.resume()

You’ll notice the 3 arguments in the block (data, response, error) match the arguments in the completion handler declaration: (NSData?, NSURLResponse?, NSError?). You can specify the types explicitly when you create your block but it’s not necessary because the compiler can figure it out. Sometimes it’s good to remember that people read code, not just computers, so it doesn’t hurt to be explicit:

let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) in  // this is where the completion handler code goes  print(response)  print(error)})task.resume()

Somewhat confusingly, you can actually drop the completionHandler: bit from the declaration and just tack the code block on after the function call. This is totally equivalent to the code above and a pretty common thing you’ll see in Swift code:

let task = session.dataTaskWithRequest(urlRequest) { (data, response, error) in  // this is where the completion handler code goes  print(response)  print(error)}task.resume()

If you want to ignore some arguments you can tell the compiler that you don’t want them by replacing them with _:

let task = session.dataTaskWithRequest(urlRequest) { (data, _, error) in  // can't do print(response) since we don't have response  print(error)}task.resume()

We can also declare the code block as a variable then pass it in when we call dataTaskWithRequest. That’s handy if we want to use the same completion handler for multiple tasks. We used this technique when implementing an OAuth 2.0 login flow, since it has lots of steps but we want to handle any of them failing similarly.

Here’s how you can use a variable for a completion handler:

let myCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> Void = { (data, response, error) in  // this is where the completion handler code goes  print(response)  print(error)}let task = session.dataTaskWithRequest(urlRequest, completionHandler: myCompletionHandler)task.resume()

When Does a Completion Handler Get Called?

What’ll happen to our little code block? Well, it won’t get called right away when we call dataTaskWithRequest. But somewhere in Apple’s implementation of dataTaskWithRequest it will get called like this:

// make an URL request// wait for results// check for errors and stuffcompletionHandler(data, response, error)// return

You don’t need to write that in your own code, it’s already implemented in dataTaskWithRequest. In fact, there are probably a few calls like that for handling success and error cases. The completion handler will just sit around waiting to be called whenever dataTaskWithRequest is done.

Implementing a Useful Completion Handler

So what’s the point of completion handlers? Well, we can use them to take action when something is done. Like here we could set up a completion handler to print out the results and any potential errors so we can make sure our API call worked. Let’s go back to our dataTaskWithRequestexample and implement a useful completion handler. Here’s where the code will go:

let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in  // do stuff with response, data & error here})task.resume()

Now we have access to 3 arguments: the URL response, the data returned by the request and an error (if one occurred). Here’s the completion handler that we previously set up to:

  1. Make sure we got data and no error
  2. Try to transform the data into JSON (since that’s the format returned by the API)
  3. Access the post object in the JSON and print out the title

You’ll need to add import Foundation at the top of your file to have access to NSJSONSerialization.

let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in  guard let responseData = data else {    print("Error: did not receive data")    return  }  guard error == nil else {    print("error calling GET on /posts/1")    print(error)    return  }  // parse the result as JSON, since that's what the API provides  let post: NSDictionary  do {    post = try NSJSONSerialization.JSONObjectWithData(responseData,       options: []) as! NSDictionary  } catch  {    print("error trying to convert data to JSON")    return  }  // now we have the post, let's just print it to prove we can access it  print("The post is: " + post.description)    // the post object is a dictionary  // so we just access the title using the "title" key  // so check for a title and print it if we have one  if let postTitle = post["title"] as? String {    print("The title is: " + postTitle)  }})task.resume()

Which prints out:

The post is: {  body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto";  id = 1;  title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit";  userId = 1;}The title is: sunt aut facere repellat provident occaecati excepturi optio reprehenderit

And That’s All About Completion Handlers

Hopefully that demystifies completion handlers and code blocks in Swift for you. If you have any questions just leave a comment below and I’ll respond as soon as I can.

If you’d like more Swift tutorials on topics like this one, sign up below to get them sent directly to your inbox.

1 0