Async Functions Are Coming to Swift
And you are going to like it.
Introduction
Old iOS developers remember the days where Objective C didn’t support blocks just yet. Async operations were a nightmare.
Blocks improved async operations, and when Swift appeared, closures were already natural for us.
But to help us get through async calls, closures still have their pitfalls.
Fortunately, a new feature comes to Swift, called the Async function, and it will make our lives much better.
But what’s the problem with closures?
Closures are fine, really, and they served us well over the years. But we all know async + closures have their issues.
Pyramid of Doom
Let’s just take a look at the following snippet:
func processImageFromServer(completionBlock: (_ result: UIImage?) -> Void) { loadFileFromServer("myFile") { fileData in analyzFile(fileData) { imageData in decodeImage(imageData) { image in scaleDownImage(image) { newImage in completionBlock(newImage) } } } } } processImageFromServer { image in self.updateUI(withNewImage: image) }
We all know this example, and it is called the “pyramid of doom”.
While using several nested closures seems like an elegant idea in the first place, it still leads to some problems.
One problem we all know when dealing with nested closures is error handling.
Take a look at the following code snippet:
func processImageFromServer(completionBlock: (_ result: UIImage?) -> Void) { loadFileFromServer("myFile") { fileData in guard let fileData = fileData else { completionBlock(nil) return } analyzFile(fileData) { imageData in decodeImage(imageData) { image in scaleDownImage(image) { newImage in completionBlock(newImage) } } } } }
Do you see the call for the completionBlock and the return statement afterward? Now, imagine you need to that for each closure, and not only that, must not forget the return statement. Otherwise, you may encounter some serious issues which are hard to follow.
Using an enum and a switch statement can results an even uglier and more complicated code, and don’t get me even started with throwing functions:
func processImageFromServer(completionBlock: (_ result: UIImage?) -> Void) { loadFileFromServer("myFile") { fileData in do { analyzFile(fileData) { imageData in do { decodeImage(imageData) { image in do { scaleDownImage(image) { newImage in completionBlock(newImage) } } catch { completionBlock(nil) } } } catch { completionBlock(nil) } } } catch { completionBlock(nil) } } }
I think the point is clear by now.
Fortunately, things are about the change soon with the two best bodies – async and await.
Meet async and await
Async and await are not something new at all. Microsoft had those features for many years with .Net, and JavaScript developers know this feature closely.
Regarding Swift, developers proposed to add that feature for almost five years (!), and finally, it got approved.
This is how it looks like:
func loadFileFromServer(string : String) async ->Data // this is the async declaration let fileData = await loadFileFromServer("myFile") // this is the call.
That’s it? That’s it.
We add the word “async” before returning a value when declaring the function and “await” before calling it.
If it’s familiar to you somehow, that’s because it looks similar to declaring functions as throwing.
Now let’s see the above code with the new async/await mechanism:
func processImageFromServer(string : String) async ->UIImage { let fileData = await loadFileFromServer(string) let imageData = await analyzFile(fileData) let image = await decodeImage(imageData) return image }
Isn’t that simple?
Using “await” suspends the current thread and wait for an answer. This makes the code much more apparent and straightforward.
Something sounds weird
Ok, so we know we can write async code that looks and behave like a synchronized code.
But when you think of it, something is weird –
We said that we have a new operator called “await,” and it suspends the current thread.
One of the most popular use cases for using async operations is not to block the UI thread.
If the UI Thread calls to an async function and uses “await”, it means the UI thread is now blocked! So, what’s the point?
Well, it doesn’t work like that exactly.
Apparently, there are two types of functions now –
One is the async function, marked with an “async”, and the second one is the function that calls the async function and includes the “await” operator. That function has the @asyncHandler operator.
The “body” of the @asyncHandler functions is suspended, but the function that calls the @asyncHandler function is not suspended and continues without waiting for a response.
I will try to explain that both in code and diagram.
Now let’s see it in code:
func loadImage() async ->UIImage? { do { print("6") let imageData = try Data(contentsOf: URL(string: "https://images.radio.com/myImage.png")!) print("7") return UIImage(data: imageData) } catch { } return nil } @IBAction func tapped(_ sender: Any) { print("1") startLoadImage() print("2") } @asyncHandler func startLoadImage() { print("3") if let image = await loadImage() { print("4") DispatchQueue.main.async { self.imageView.image = image } } print("5") }
When tapped() function fired, the order of the print messages in the console looks like this:
1
2
3
6
7
4
5
And this is critical to understanding – functions that contain the “await” keyword are much like completion blocks. When you call such a function, the run loop continues, and this way, the UI thread is not blocked.
A question – what if the @asyncHandler function returns a value? Well, this is an impossible situation. @asyncHandler cannot return values, and they can only be void functions.
Summary
Async and Await are great news for Swift developers. It’s another step to make Swift a modern language and to simplify life for all of us.
But, since it’s a significant change from what we used to – we need to understand precisely how it works underneath. Read the code snippet carefully to absorb how to use it fully.
Senior iOS Developer | 5 YOE | Swift | UIKit | SwiftUI | Helping create innovative iOS Apps
4 年Short and sweat. Super excited for this.
Software Engineer at Starling Bank
4 年No more pyramid within a pyramid? How will life be the same again? ??
Senior iOS Developer at TinyTap
4 年Great news ??!
Mobile Team Lead at Houzz
4 年Thanks, Avi, that's great!!