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

5

35

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

Chordophone answered 3/5, 2021 at 19:54 Comment(2)
Xcode 12.5 is Swift 5.4 so exactly what are we talking about? Did you install a different toolchain?Mulry
Yep, I downloaded the Swift 5.5 Development Snapshot toolchainChordophone
S
32

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.

Stlouis answered 8/6, 2021 at 23:3 Comment(8)
How to call async fun into onAppear for iOS14?Thunder
I believe you don’t. It’s iOS15+ api. But strangely currently Xcode Beta 1 let’s me deploy to my iPhone which is iOS14.5. But it crashes as soon as some Async/Await code is called.Strepphon
I don't remember where I read it's possible via actor.Thunder
My previous post is wrong. Apple developers promise to solve problems to run this feature on iOS 14 and lower. Waiting for the next Xcode betas.Thunder
The promise to look into it. Not promise to change it. It’s very hard. Look at this: forums.swift.org/t/…Strepphon
I've read in release notes of Xcode beta 2. Apple considers it is issue but not feature. Known Issues Swift Concurrency requires a deployment target of macOS 12, iOS 15, tvOS 15, and watchOS 8 or newer. (70738378)Thunder
Deprecated (using Xcode 13.2.1): 'async(priority:operation:)' is deprecated: 'async' was replaced by 'Task.init' and will be removed shortly.Heavyweight
Also see Adams answer below https://mcmap.net/q/424012/-how-can-i-use-async-await-with-swiftui-in-swift-5-5Heavyweight
C
41

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

Corrientes answered 25/5, 2021 at 18:57 Comment(2)
I've totally lived through real code that did exactly this kind of thing (think successive online database calls that depend on one another). Combine framework saved me in the end, but async/await is really going to save me. The truth is that 95% of programmers seem not even to understand what asynchronous means (hence my programmingios.net/what-asynchronous-means), and with async/await, they won't have to!Mulry
@Mulry I’ve only been here for around a year and literally a quarter of all questions are because of completion handlers. Looking forwards to async/await as well!Ubana
S
32

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.

Stlouis answered 8/6, 2021 at 23:3 Comment(8)
How to call async fun into onAppear for iOS14?Thunder
I believe you don’t. It’s iOS15+ api. But strangely currently Xcode Beta 1 let’s me deploy to my iPhone which is iOS14.5. But it crashes as soon as some Async/Await code is called.Strepphon
I don't remember where I read it's possible via actor.Thunder
My previous post is wrong. Apple developers promise to solve problems to run this feature on iOS 14 and lower. Waiting for the next Xcode betas.Thunder
The promise to look into it. Not promise to change it. It’s very hard. Look at this: forums.swift.org/t/…Strepphon
I've read in release notes of Xcode beta 2. Apple considers it is issue but not feature. Known Issues Swift Concurrency requires a deployment target of macOS 12, iOS 15, tvOS 15, and watchOS 8 or newer. (70738378)Thunder
Deprecated (using Xcode 13.2.1): 'async(priority:operation:)' is deprecated: 'async' was replaced by 'Task.init' and will be removed shortly.Heavyweight
Also see Adams answer below https://mcmap.net/q/424012/-how-can-i-use-async-await-with-swiftui-in-swift-5-5Heavyweight
J
14

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()
}
Jack answered 31/10, 2021 at 23:27 Comment(0)
G
2
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()
        }
    }
}
Goth answered 31/12, 2021 at 6:53 Comment(0)
D
0

Call this simplified .task { } version from SwiftUI View

var body: some View {
     VStack(spacing:.default) {
          yourCustomView
        }
        .padding()
      }
      .task { await viewModel.onAppear() }
    }

And in the ViewModel

func onAppear() async {
    await getStuffFromNetwork()
 }
Donnelly answered 15/2, 2023 at 12:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.