Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use async/await with SwiftUI in Swift 5.5?

I have been testing the async/await functionality previewed in the Swift 5.5 release, but I am unable to collect the results from an async function and display them using SwiftUI. Here is my code:

import SwiftUI

struct AsyncTestView: View {
    @State var text: String?

    // Async function
    func asyncGetText() async -> String {
        Thread.sleep(forTimeInterval: 10)
        return "My text"
    }
    
    // Stores the result of async function
    func fetchText() async {
        let text = await asyncGetText()
        DispatchQueue.main.async {
            self.text = text
        }
    }
    
    var body: some View {
        Text(text ?? "Loading")
            .onAppear(perform: fetchText)
    }
}

This results in the following error:

'async' call in a function that does not support concurrency
Add 'async' to function 'fetchText()' to make it asynchronous

Adding async to the fetchText() function then results in the following error on the .onAppear() function:

Invalid conversion from 'async' function of type '() async -> ()' to synchronous function type '() -> Void'

In this article, they use the @asyncHandler tag to annotate the fetchText function, however this results in the warning: '@asyncHandler' has been removed from the language'.

like image 752
mistry Avatar asked May 03 '21 19:05

mistry


People also ask

What is async await in Swiftui?

Using URLSession and async/await This means we'll have to use await whenever we call this method. Doing so indicates a so-called suspension point, and gives the runtime the opportunity to suspend the currently executing function. A suspended function is “put on hold” until the function it called returns.

Does async await require iOS 15?

This means you can write your own code to use async/await, actors, and so on, but you won't automatically gain access to the new Foundation APIs using those – things like the new URLSession APIs that use async/await still require iOS 15.

Can I use async await in iOS 13?

You can now use Swift Concurrency in applications that deploy to macOS Catalina 10.15, iOS 13, tvOS 13, and watchOS 6 or newer. This support includes async/await, actors, global actors, structured concurrency, and the task APIs.

When was async await introduced in Swift?

SE-0296 introduced asynchronous (async) functions into Swift, allowing us to run complex asynchronous code almost is if it were synchronous.


Video Answer


4 Answers

I'm the author of the article you referenced.

As discussed in Discover concurrency in SwiftUI, views can make use of the new .task { } and .refreshable { } modifiers to fetch data asynchronously.

So you now have the following options to call your async code:

func someSyncMethod() {
  doSomeSyncWork()
  Task {
    await methodThatIsAsync()
  }
}
List {
}
.task {
  await methodThatIsAsync()
}
List {
}
.refreshable {
  await methodThatIsAsync()
}

If you're using a separate view model, make sure to mark it as @MainActor to ensure property updates get executed on the main actor.

I updated the code for my article: https://github.com/peterfriese/Swift-Async-Await-Experiments

like image 100
Peter Friese Avatar answered Oct 27 '22 19:10

Peter Friese


As per new informations in WWDC session Meet async/await in Swift WWDC21, at 23m:28s this is now done using:

Task {
   someState = await someAsyncFunction()
}

Screenshot from the session.

Image showing how to use an Async task on a sync context

Note that there are more ways to instantiate a task. This is the most basic. You can use Task.detached as well and both ways can get a priority argument. Check both the Task docs and the session Check Explore structured concurrency in Swift WWDC21 at around 23:05 (I recommend the whole session!) for more info.

like image 26
Nuno Gonçalves Avatar answered Oct 27 '22 21:10

Nuno Gonçalves


I agree with @peter-friese's answer but will add for those reading this the change of syntax when bridging from sync to async:

The new syntax:

Task {
   someState = await someAsyncFunction()
}

Replaces this syntax:

async {
   someState = await someAsyncFunction()
}

... and the Task() initialiser can accept a priority parameter:

Task(priority: .userInitiated) {
   someState = await someAsyncFunction()
}
like image 14
Adam Avatar answered Oct 27 '22 19:10

Adam


import SwiftUI

struct AsyncTestView: View {
    @State var text = "Loading"

    // Async function
    func asyncGetText() async -> String {
        try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
        return "My text"
    }
    
    var body: some View {
        Text(text)
        .task {
             text = await asyncGetText()
        }
    }
}
like image 2
malhal Avatar answered Oct 27 '22 20:10

malhal