Home > Software engineering >  How can I convert to Swift async/await from GCD (DispatchQueue)?
How can I convert to Swift async/await from GCD (DispatchQueue)?

Time:11-23

I am following Stanfords' CS193p Developing Apps for iOS online course.

It is using the Grand Central Dispatch (GCD) API for a demo of multithreading. But they noted, that

"GCD has been mostly replaced by Swift's new built-in async API as of WWDC 2021".

So I wanted to learn how the code from the Lecture would look like after updating it to use this new API.

After watching Apple's WWDC videos, it seems to me like
DispatchQueue.global(qos: .userInitiated).async { } is replaced in this new async API with Task { } or Task(priority: .userInitiated) {}, but I'm not sure, what has DispatchQueue.main.async { } been replaced with?

So, my questions are:

  1. Am I correctly assuming, that DispatchQueue.global(qos: .userInitiated).async { } has been replaced with Task(priority: .userInitiated) {}
  2. What has DispatchQueue.main.async { } been replaced with?

Please help, I want to learn this new async-await API.

Here's the code from the Lecture, using old GCD API:

DispatchQueue.global(qos: .userInitiated).async {
    let imageData = try? Data(contentsOf: url)
    DispatchQueue.main.async { [weak self] in
        if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
            self?.backgroundImageFetchStatus = .idle
            if imageData != nil {
                self?.backgroundImage = UIImage(data: imageData!)
            }
            // L12 note failure if we couldn't load background image
            if self?.backgroundImage == nil {
                self?.backgroundImageFetchStatus = .failed(url)
            }
        }
    }
}

The whole function (in case you need to see more code):

private func fetchBackgroundImageDataIfNecessary() {
    backgroundImage = nil
    switch emojiArt.background {
    case .url(let url):
        // fetch the url
        backgroundImageFetchStatus = .fetching
        DispatchQueue.global(qos: .userInitiated).async {
            let imageData = try? Data(contentsOf: url)
            DispatchQueue.main.async { [weak self] in
                if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
                    self?.backgroundImageFetchStatus = .idle
                    if imageData != nil {
                        self?.backgroundImage = UIImage(data: imageData!)
                    }
                    // L12 note failure if we couldn't load background image
                    if self?.backgroundImage == nil {
                        self?.backgroundImageFetchStatus = .failed(url)
                    }
                }
            }
        }
    case .imageData(let data):
        backgroundImage = UIImage(data: data)
    case .blank:
        break
    }
}

CodePudding user response:

If you really are going to do something slow and synchronous, Task.detached is a closer analog to GCD’s dispatching to a global queue. If you just use Task(priority: ...) { ... } you are leaving it to the discretion of the concurrency system to decide which thread to run it on. (And just because you specify a lower priority does not guarantee that it might not run on the main thread.)

For example:

func fetchAndUpdateUI(from url: URL) {
    Task.detached {                          // or specify a priority with `Task.detached(priority: .background)`
        let data = try Data(contentsOf: url)
        let image = UIImage(data: data)
        await self.updateUI(with: image)
    }
}

And if you want to do the UI update on the main thread, rather than dispatching it back to the main queue, you would simply add the @MainActor modifier to the method that updates the UI:

@MainActor
func updateUI(with image: UIImage?) async {
    imageView.image = image
}

That having been said, this is a pretty unusual pattern (doing the network request synchronously and creating a detached task to make sure you don't block the main thread). We would probably use URLSession’s new asynchronous data(from:delegate:) method to perform the request asynchronously.

In short, rather than looking for one-to-one analogs for the old GCD patterns, use the concurrent API that Apple has provided where possible.

  • Related