Swift async/await 取代了 DispatchQueue.main.async

Mic*_*bro 14 async-await swift

在新的 Swift 5.5 中使用 async/await 并发机制时如何返回主线程?我应该只用@MainActor 标记函数、类吗?我还能用吗DispatchQueue.main.async?会是正确的吗?因为新机制不使用 GCD 并且异步任务和线程之间没有像以前那样的映射?

例如我正在使用ListSwiftUIrefreshable

List { }
.refreshable {
    viewModel.fetchData()
}
Run Code Online (Sandbox Code Playgroud)

这个可以吗

List { }
.refreshable {
    DispatchQueue.main.async {
      viewModel.fetchData()
    }
}
Run Code Online (Sandbox Code Playgroud)

或者我需要在 ViewModel 类上添加 @MainActor ?我在项目中没有使用 async/await,因此仅使用 MainActor 来实现这个单一的可刷新似乎是多余的,我也不知道添加此类属性如何影响 ViewModel 类的其余方法和属性,他们现在使用合并。

但另一方面 Xcode 显示

运行时:SwiftUI:不允许从后台线程发布更改;确保在模型更新时从主线程发布值(通过像 receive(on:) 这样的运算符)。

此外,在将 @MainActor 添加到 ViewModel 后,我收到多个这样的警告

与全局参与者“MainActor”隔离的属性“标题”无法满足协议“OnlineBankingListViewModelProtocol”的相应要求

Rob*_*Rob 14

你问:

\n
\n

我还能用吗DispatchQueue.main.async

\n
\n

如果您在一个async方法中并且想要将某些内容分派到主队列,那么最字面的等效内容是:

\n
MainActor.run { ... }\n
Run Code Online (Sandbox Code Playgroud)\n

但更谨慎的做法是简单地用 来标记方法(或其类)@MainActor。这不仅可以确保它在主线程上运行它,而且如果您尝试从错误的参与者调用它,您还会收到编译时警告。

\n

因此,如果您的视图模型标记为,则无需@MainActor在 上手动运行任务。MainActor在处理被观察对象的已发布属性时尤其如此。

\n

例如,考虑:

\n
@MainActor\nclass ViewModel: ObservableObject {\n    @Published var values: [Int] = []\n\n    func fetchData() async {\n        let foo = await ...\n        values = foo.values\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

进而

\n
struct ContentView: View {\n    @ObservedObject var viewModel = ViewModel()\n\n    var body: some View {\n        List {\n            ...\n        }\n        .refreshable {\n            await viewModel.fetchData()\n        }\n\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

(注意,我在其中创建了fetchData一个async方法,以便旋转器能够准确地反映进程何时运行。)awaitrefreshableasync

\n

请参阅 WWDC 2021 视频Swift 并发:更新示例应用程序。这无疑说明了 UIKit 应用程序的转变,但包括@MainActor和的示例MainActor.run

\n
\n

请注意,虽然@MainActor, 很大程度上消除了对 的需要MainActor.run { \xe2\x80\xa6 },但在某些情况下您仍然可以使用此run模式。具体来说,如果您在其他某个 actor 上并且想要@MainActor在主线程上连续运行三个单独的函数,您可以将它们的系列包装在一个单独的函数中MainActor.run { \xe2\x80\xa6 }块中,从而通过一次调度到主线程来运行所有三个函数演员,而不是三个单独的电话。

\n
\n

上面,我重点介绍了重要部分,但以下是我的完整 MCVE:

\n
struct ContentView: View {\n    @ObservedObject var viewModel = ViewModel()\n\n    var body: some View {\n        List {\n            ForEach(viewModel.values, id: \\.self) { value in\n                Text("\\(value)")\n            }\n        }\n        .refreshable {\n            await viewModel.fetchData()\n        }\n\n    }\n}\n\nstruct Foo: Decodable{\n    let json: [Int]\n}\n\n@MainActor\nclass ViewModel: ObservableObject {\n    @Published var values: [Int] = []\n\n    func fetchData() async {\n        do {\n            let foo = try await object(Foo.self, for: request)\n            values = foo.json\n        } catch {\n            print(error)\n        }\n    }\n\n    func object<T: Decodable>(_ type: T.Type, for request: URLRequest) async throws -> T {\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let response = response as? HTTPURLResponse else {\n            throw URLError(.badServerResponse)\n        }\n\n        guard 200 ... 299 ~= response.statusCode else {\n            throw ApiError.failure(response.statusCode, data)\n        }\n\n        return try JSONDecoder().decode(T.self, from: data)\n    }\n\n    var request: URLRequest = {\n        let url = URL(string: "https://httpbin.org/anything")!\n        var request = URLRequest(url: url)\n        request.httpMethod = "POST"\n        request.httpBody = "[1,2,3,4,5]".data(using: .utf8)\n        request.addValue("application/json", forHTTPHeaderField: "Content-Type")\n        request.addValue("application/json", forHTTPHeaderField: "Accept")\n\n        return request\n    }()\n}\n\nenum ApiError: Error {\n    case failure(Int, Data)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 这应该是公认的答案。不仅准确描述了 OP 的要求,还提供了编译时检查的其他最佳实践。 (3认同)

sca*_*aly 9

的替代品DispatchQueue.main.async { foo.bar() }是:

Task { @MainActor in 
    print(Thread.current.isMainThread) // "true"
}
Run Code Online (Sandbox Code Playgroud)