Ease the burden of asynchronous programming in Swift with AwaitKit

As you may know, in addition to my love for the Swift language, I also love to work with Nodejs and Javascript. In my last blog post I talk about the ES7 Async functions and why  I think it is an amazing feature in order to make the async code more readable and easy to write. I was frustrated to not have these kind of features with the Swift language so I decided to write my own library: AwaitKit. In this post I’ll expose you the philosophy behind AwaitKit, how to it works and how to use it.

AwaitKit

The AwaitKit library aims to make the write of asynchronous code more easy by letting write it like synchronous one. Here a little example:

// Write this kind of code
do {
  let user  = try await(User.logIn(username, password))
  let posts = try await(Posts.fetchWithUser(user))

  handlePosts(posts)
}
catch {
  handleError(error)
}

// Instead of this one

User.logIn(username, password) { user, error in
  if let error = error {
    self.handleError(error)
  }
  else {
    Posts.fetchWithUser(user) { posts, error in
      if let error = error {
        self.handleError(error)
      }
      else {
        self.handlePosts(posts)
      }
    }
  }
}

This example illustrates the philosophy of AwaitKit and how it works by the same time. In the both cases we are trying to log in a user and then to retrieve his blog posts. Of course errors can occurred during the process and we take care of them.

AwaitKit, in addition of the await method, also comes with another useful method named async which executes the given closure in background and returns a promise as result:

func postsForUserWithName(username: String, password: String) -> Promise<[Post]> {
  return async {
    let user  = try await(User.logIn(username, password))
    let posts = try await(Posts.fetchWithUser(user))

    return posts
  }
}

In this example, we are creating a postsForUserWithName method which will return in a “future” a list of Post. The main part of this method is the async closure which will be wrapped into a promise and returns. The advantage here is the async takes care of exceptions and reject the promise if necessary. To perform promises, AwaitKit uses PromiseKit.

The promise returned can be used later to manage its result:

let promise = postsForUserWithName("yannickl", password: "awaitkit")

promise.then { posts in
  print(posts)
}
.error { error
  print(error)
}

If you already understood the powerful of the mechanism, here is a neat alternative:

let posts = try! await(postsForUserWithName("yannickl", password: "awaitkit"))

Under the hood

The main part of the library is to wait for the promise is resolved (or rejected). To do so the code in the async or await blocks are executed on a background queue and then we use semaphores to wait for a completion.

Here is the core part:

let semaphore = dispatch_semaphore_create(0)

promise
  .then(on: queue) { value -> Void in
    dispatch_semaphore_signal(semaphore)
  }
  .error(on: queue) { err in
    dispatch_semaphore_signal(semaphore)
  }

dispatch_semaphore_wait(semaphore, UINT64_MAX)

As you can see, the semaphore helps you to block the current thread until the promise resolved or rejected. There is two important points to take into consideration here:

  • If you call this code on the main thread it’ll be blocked until the promise did resolved. So you should never call this code on the main thread!
  • If the queue passed into parameter of the promise is the same queue of the execution scope you will enter into a deadlock because the thread is blocked with the use of dispatch_semaphone_wait and the promise will never be able to resolve.

The other parts of the library mainly focus to wrap blocks into promises.

Examples

In this section we are going to see some example to help you to understand how to use AwaitKit. First of all let’s define a useful method for our examples:

func request(method: Method, url: URLStringConvertible) -> Promise<NSData> {
  return Promise{ resolve, reject in
    Alamofire.request(method, url).response { (_, _, data, error) in
      if let error = error {
        reject(error)
      }
      else {
        resolve(data)          
      }
    }
  }
}

We are using Alamofire to make HTTP request, and here we are wrapping the request call into a promise in order to be reusable with AwaitKit.

Now you can retrieve the content of a url writing this kind of code:

do {
  let content = try await(request(.GET, "http://yannickloriot.com"))

  print(content)
}
catch {}

// Or you can also use the async block
async {
  let content = try await(self.request(.GET, "http://yannickloriot.com"))

  print(content)
}

// If you want use the result later
let result = async {
  return try await(self.request(.GET, "http://yannickloriot.com"))
}

result.then { content in 
  print(content)
}

In this example we are investigating three possibilities to use AwaitKit for the same purpose. In the first case you need to manage yourself the exceptions but it lets you more control and the code is “synchronous” thanks to the await method. In the second case, we use an async block which take care of the exceptions but the code is running asynchronous outside its scope. Concerning the last case, you get the promise returned by the async block and you can use the result later.

If you need more control on which queue the async or await run you can pass it into parameter when you call them:

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

async(on: queue) {
  let content = try await(on: queue, self.request(.GET, "http://yannickloriot.com"))
}

The last thing, and it may evolve in the time, you can also use directly block code instead of promises with the await method. The API may change in the future but here an example to allow you to understand its use:

let message = try await {
  NSThread.sleepForTimeInterval(2)

  return "Eh oh!"
}

print(message) // Display "Eh oh!"

Here the “Eh oh!” message is returned after 2 seconds. But thanks to the await method everything works like in a synchronous mode and the main thread is not blocked during the 2 seconds of sleep.

Conclusion

This post was an introduction to the AwaitKit library. It aims to make the write of asynchronous code easier and I believe it does. If there is something you didn’t understand or improvements to do, don’t hesitate to let a comment below or to add an issue or a pull request to the github repo.

Happy coding!

1 Star2 Stars3 Stars4 Stars5 Stars (15 votes, average: 4.80 out of 5)
Loading...

One comment

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.