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 a 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.

No alt text provided for this image

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. 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s