Refactor A Private Callback Based API To Use Async/Await
Last time we talked about how to refactor a callback based network call into an async/await based call. In that case, we had access to the underlying networking code & updated the entire chain of method calls to use async/await. But what if weâre using a private API that only has callback based methods? It may sound tricky but Apple has provided us with some easy ways to do this.
In my previous post, I used the Star Wars API to retrieve a few characters from the movies. In this example, weâll pretend that weâve pulled in a PrivateStarWarsAPI framework which has callback based methods only. Weâll add our own AsyncAPI layer to transform a callback based method into an async method. Letâs jump into the code.
Below is the loadPeople() callback based method in the PrivateStarWarsAPI. Remember that âwe canât change this methodâ in our example:
If we wanted to use this method directly, our code would look something like this:
Thatâs not too bad, but we can use async/await to simplify the code further. Letâs create a wrapper class that transforms the callback based logic into async logic:
Thankfully our method only needs 3 lines of logic to convert a callback based method into an async one. Notice the call named withCheckedThrowingContinuation - this is the transition point from callback logic to async logic. This is a top level method available in the Foundation framework and it tells the system to offload async work and resume when necessary. I used the throwing version, but we could also use withCheckedContinuation if our callback will never return an error (if thatâs the case, we would also remove the throws and try keywords).Â
Letâs talk a little more about continuations. âCheckedâ means Swift will throw a fatal error if you accidentally call continuation.resume() more than once and it will point you to your mistake (resume() must be called exactly once). If you fail to call continuation.resume(), youâll see a console message that looks like this:
SWIFT TASK CONTINUATION MISUSE: loadPeopleAsync() leaked its continuation!
Itâs usually best to use checked continuations so that you can receive feedback on continuation errors before you send your app into the wild. If youâre certain your logic is bulletproof and you need a tiny improvement in performance, there are also unchecked versions of these methods, named withUnsafeContinuation and withUnsafeThrowingContinuation. If you use these unchecked versions, your app will probably still crash when calling resume() multiple times & you wonât get notified when you never call resume() and leak device resources.
Ok, back to our API - hereâs what our code looks like when we use our new async API wrapper:
Itâs pretty awesome how simple the code is when using async/await. In a single line we wait for the API result as well as handle any errors that may have been returned. All we had to do was write a simple wrapper method to bridge the callback & async worlds.
I hope you enjoyed this post & learned something new! I know I did while I tinkered with continuations this week. Hereâs a link to my sample project in GitHub that contains the code in this post. See you next time!