从任务中获取结果

Getting Result out of a Task

我有一个异步函数,它 returns App Store 查询的结果(查询成功或未显示在警报中)。我无法从任务中获取结果--无法在范围 中找到'result'。如果我移动 let gotTitle = result ? “成功”:“请求失败” 在任务中我看到警告“从未使用不可变值 'gotTitle' 的初始化”。

有关 Task 的 Apple 文档讨论了使用值或结果从任务中获取数据,但不清楚如何执行此操作。我还使用调试器单步调试了代​​码。它在任务中正确显示 result = true 和 gotTitle = Success。

struct TestAlert: View {

    var gotTitle: String = ""

    @State private var presentAlert = false

    var body: some View {

        VStack {

            Button(action: {
                Task {

                    let result = await restore()
                    print (result)
                    
                }
                let gotTitle = result ? "Success" : "The Request Failed"
                print(gotTitle)
            }) {
                Text("Restore Purchase")
            }
        }
        .alert(gotTitle, isPresented: $presentAlert, actions: {

        })

    }
    func restore() async -> Bool {

            return ((try? await AppStore.sync()) != nil)

    }
}

虽然不建议您像这样组合视图和业务逻辑(SwiftUI 更喜欢将其放在某种 ObservableObject 后面),但您可以像使用完成处理程序一样使用 Task将完成的工作放入其中。

Task { @MainActor in // SwiftUI requires state is updated on the main queue.
  // Perform async work.
  // Update state.
}

你说:

I am having trouble getting the result out of the task

Cannot find 'result' in scope.

是的,如您的问题所述,result 是任务内部的局部变量,因此您不能在 Task 之外引用它(除非您保存它,或者标题字符串,在struct).

的属性中

If I move let gotTitle = result ? "Success" : "The Request Failed" to within the task I see the warning "Initialization of immutable value 'gotTitle' was never used".

是的,您已将 gotTitle 定义为局部变量,恰好与同名的 属性 具有相同的名称。


所以,在我开始解决之前,让我们做一些观察,以便我们了解发生了什么。关键问题是 Task 里面有 await 异步运行 考虑:

var body: some View {
    VStack {
        Button(action: {
            Task {
                print(Date(), "started task")
                let result = await restore()
                let gotTitle = result ? "Success" : "The Request Failed"  // a local variable; we'll fix this in the next example
                print(Date(), "task finished", gotTitle)
            }
            print(Date(), "after task submitted")
        }) {
            Text("Restore Purchase")
        }
    }
    .alert(gotTitle, isPresented: $presentAlert, actions: { })
}

请注意,我已将 let gotTitle 移到 Task 中。您不能在该代码块之外引用 result

无论如何,当您点击按钮时,您将在控制台中看到以下内容:

2022-04-18 00:38:03 +0000 after task submitted
2022-04-18 00:38:03 +0000 started task
2022-04-18 00:38:06 +0000 task finished Success

注意事件的顺序:

  • “开始的任务”出现after“after task submitted”
  • “任务完成”在“开始任务”几秒后出现

希望这能说明为什么引用 result 打印“任务提交后”的位置毫无意义。到那时你甚至还没有达到 result 的 declaration/assignment。

所以,这个故事的寓意是,如果你想在异步任务之后更新一些东西,它需要紧接在 await 行之后,在 Task 内(或者它的任何上下文)在)。如果将它放在 Task 之外,则意味着它不会等待异步 Task 完成。


那么,您将如何从 Task 块中访问结果。您可以将其保存到 ObservedProperty(巧合的是,这有助于将业务逻辑与视图分开):

struct TestAlert: View {
    @ObservedObject var restoreRequest = AppStoreRestoreRequest()
    @State var isPresented = false

    var body: some View {
        VStack {
            Button {
                Task {
                    await restoreRequest.restore()
                    isPresented = true
                }
            } label: {
                Text("Restore Purchase")
            }
        }
        .alert(restoreRequest.state.title, isPresented: $isPresented, actions: { })
    }
}

class AppStoreRestoreRequest: ObservableObject {
    @Published var state: State = .notDetermined

    func restore() async {
        let result = (try? await AppStore.sync()) != nil
        state = result ? .success : .failure
    }
}

extension AppStoreRestoreRequest {
    enum State {
        case notDetermined
        case success
        case failure
    }
}

extension AppStoreRestoreRequest.State {
    var title: String {
        switch self {
        case .notDetermined: return "Not determined."
        case .success:       return "Success."
        case .failure:       return "The request failed."
        }
    }
}

因此,请求的性能(和当前状态)被拉出视图,请求的状态被捕获在 AppStoreRestoreRequest.

这里是固定变体

struct TestAlert: View {

    @State var gotTitle: String = ""          // << state !!
    @State private var presentAlert = false

    var body: some View {

        VStack {

            Button(action: {
                Task {

                    let result = await restore()
                    print (result)
                    await MainActor.run {     // << update on Main !!
                        gotTitle = result ? "Success" : "The Request Failed"
                        print(gotTitle)
                    }
                }
            }) {
                Text("Restore Purchase")
            }
        }
        .alert(gotTitle, isPresented: $presentAlert, actions: {

        })

    }
    func restore() async -> Bool {
        return ((try? await AppStore.sync()) != nil)
    }
}