Async Functions Are Coming to Swift

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.

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. 

Austin Betzer

Senior iOS Developer | 5 YOE | Swift | UIKit | SwiftUI | Helping create innovative iOS Apps

4 年

Short and sweat. Super excited for this.

Avi Gray

Software Engineer at Starling Bank

4 年

No more pyramid within a pyramid? How will life be the same again? ??

Mickael Belhassen

Senior iOS Developer at TinyTap

4 年

Great news ??!

Eyal Silberman

Mobile Team Lead at Houzz

4 年

Thanks, Avi, that's great!!

要查看或添加评论,请登录

Avi Tsadok的更多文章

  • Boost Your Loop Performance By 87%

    Boost Your Loop Performance By 87%

    Today’s devices are incredibly powerful, often leading us to overlook the importance of efficiency and optimizations…

    2 条评论
  • Explore the Dynamic Island — ActivityKit Tutorial

    Explore the Dynamic Island — ActivityKit Tutorial

    Apple wowed us with the iPhone 14 Pro presentation in a way no one expected. The team from Cupertino took a hardware…

    1 条评论
  • Where Is My?Logic?

    Where Is My?Logic?

    “Describe to me your app architecture”. “Oh, it’s easy.

    2 条评论
  • Swift Result Builders – A Giant Leap Forward to produce a beautiful code

    Swift Result Builders – A Giant Leap Forward to produce a beautiful code

    The most noticeable thing about SwiftUI is that it looks incredible. It takes a relatively big chunk of code and…

    6 条评论
  • Swift 5.7: Unwrapping Optionals Gets Improvement

    Swift 5.7: Unwrapping Optionals Gets Improvement

    Introduction Unwrapping optional variables in Swift is one of the most common development tasks iOS developers perform…

    4 条评论
  • Creating a Custom Core Data Store

    Creating a Custom Core Data Store

    This tutorial is a special one. Its primary goal is to unleash how Core Data works under the hood, not to solve you a…

  • How Apple Screwed Up with SwiftUI and Core Data

    How Apple Screwed Up with SwiftUI and Core Data

    Introduction A few months ago, I decided that my third iOS development book will focus on Core Data, which is Apple’s…

    2 条评论
  • My Personal Takes from Using Combine

    My Personal Takes from Using Combine

    Confession I have a confession I want to begin with — before adopting any new technology or design pattern, I need to…

  • Reorder Items in SwiftUI LazyVStack

    Reorder Items in SwiftUI LazyVStack

    Introduction Those of you who are familiar with UIKit iOS 11 Drag and Drop API probably know that it dramatically…

  • UIKit Animations Are Messy. Here's Why SwiftUI May Fix It.

    UIKit Animations Are Messy. Here's Why SwiftUI May Fix It.

    Not only do iOS animations feel and look great, but they are also very easy to create.So how come I have the nerve to…

社区洞察

其他会员也浏览了