Asynchronous Programming in Swift
In this post, I will use these following third parties to complete the project:
- Alamofire: A HTTP networking framework in Swift.
- SwiftyJSON: To process JSON data.
- SwiftGifOrigin: An UIImage extension to display Gif files.
- Bolts-Swift: Was designed by Parse and Facebook, I use it to create asynchronous methods.
- PromiseKit: A framework helps us to simplify asynchronous programming.
- Giphy’s APIs for searching and downloading gif images.
Getting Started
Asynchronous methods, (Async for short), are the methods that not immediately returning results like most method, the async methods take some time to produce results.
I often use callbacks to deal with asynchronous methods like scanning Bluetooth devices or retrieving some resources from the internet. In fact, callback is a bad programming technique. Callback will make our code hard to read, hard to debug and take much more time to maintain later. In the end, our code will turn into something that we call the callback hell.
In this post, I will create a project using one by one technique to explain why I said callback is bad.
Firstly, go ahead and create a project, named it as whatever you like, then install these Pod frameworks to your project. You also need to edit the NSAllowsArbitraryLoads
key to YES
in NSAppTransportSecurity
dictionary in the info.plist file to specify which domains are excepted from the rules you define for App Transport Security. In our case, this is the giphy domain.
Allow HTTP requests for only giphy domain
1 | <key>NSAppTransportSecurity</key> |
Or allow HTTP requests for all domains, it is not a good idea.
1 | <key>NSAppTransportSecurity</key> |
Let’s create a class named ImageLoader
. This class contains two methods that help us to fetch and download gif images from the Giphy server.
1 | // |
The first version: Using callback
Firstly, we need to define two callbacks, which will be passed to the fetchImage
and downloadImage
methods.
1 | public typealias FetchImageBlock = (URL?, Error?) -> Void |
Then, we implement these two methods:
fetchImage
takes a keyword and a callback as params, sends a request to the Giphy server to query all images that match the keyword, gets the first one and finally returns the download url via the callback.downloadImage
takes an url and a callback as params, then uses theAlamofire
framework to download the image. Finally, returning the destination url, where the image is saved, via the callback.
1 | func fetchImage(keyword: String, callback: @escaping FetchImageBlock) { |
1 | func downloadImage(url: URL, callback: @escaping DownloadImageBlock) { |
Inside the main view controller, let’s define a method called searchImageWithKeyword
. This method takes a keyword as a param, then pass the param to the fetchImage
method of an instance of the ImageLoader
class. We also need to pass a callback to handle the results.
Inside the fetchImage
callback, let’s check if there are any errors. If it is, then we stop calling the next method, downloadImage
. Otherwise, we call the downloadImage
of the imageLoader
object. Then pass the url and a callback as params.
Inside the downloadImage
callback, let’s check if there are any errors. If it is, then we stop calling the next one. Otherwise, we update the image view on the main view by calling the updateImageAtURL
method.
1 | func searchImageWithKeyword(keyword: String) { |
1 | func updateImageAtURL(url: URL) { |
As you can see, the searchImageWithKeyword
is quite complex with many if
and else
statements inside the method. We have to check errors in many lines of codes. Imagine how complex it would be if we had more than three methods inside itself?
Build and run the project. Enter a keyword you want to search on the Giphy server, press search button then you will see the first result.
A better solution: Using Bolts
Bolts is a framework that was designed by Parse and Facebook, I use it to create asynchronous methods, without using callback. Bolts framework lets we write code as a series of actions based on events.
1 | func fetchImage(keyword: String) -> Task<URL>! { |
1 | func downloadImage(url: URL) -> Task<URL>! { |
Let’s see how simple the searchImageWithKeyword
would be by using Bolts.
1 | func searchImageWithKeyword(keyword: String) { |
Build and run the project, nothing changed. But the code is more readable than the first one, isn’t it? We gather all the errors in one place, also separate error handling and success code.
A much better solution: Using PromiseKit
One thing I do not like about Bolts framework is the lack of documentation and example projects. When I first use Bolts framework, I was very hard to get used to with the APIs of the Task object.
At the Swift Summit conference 2017, there was one speaker introduced a Framework to deal with async methods, PromiseKit. After the conference, I replaced the code using Bolts framework by PromiseKit at the projects in my company. I realize my code now more readable. I think PromiseKit’s writing will be more familiar to developers than Bolts’s writing, especially those who have worked with Javascript like me.
An async method created by using PromiseKit returns a new generic Promise, which is the primary class provided by PromiseKit. Its constructor takes a simple execution block with two parameters:
- fulfill: A function to call when the desired value is ready to fulfill the promise.
- reject: A function to call if there is an error.
Let’s apply PromiseKit to our project
1 | func fetchImage(keyword: String) -> Promise<URL> { |
1 | func downloadImage(url: URL) -> Promise<URL> { |
And the final result, what a beautiful code! :))
1 | func searchImageWithKeyword(keyword: String) { |
A feature that I find very interesting in both frameworks, Bolts and PromiseKit, is that they allow our code run on a given thread (Main thread or background thread). This is a great feature as most of the work done in the view controller has been to update the UI. Sometimes, long-running tasks are best handled on a background thread, so as not to tie up the UI. For more details about this Thread feature, please refer to their documents: #Threading
Conclusion
Since I am working on CoreBluetooth, I often have to work with async methods. Too many callbacks make my project more difficult to understand and difficult to debug if errors occur. Promise
make my code become a more beautiful girl ;).
You can download the fully finished sample project here.
Feel free to leave out your comments on my post.