[英]Swift async/await what it the replacement of DispatchQueue.main.async
在新的 Swift 5.5 中使用异步/等待并发机制时如何返回主线程? 我是否应该用@MainActor 标记 function、class。 我还能使用DispatchQueue.main.async
吗? 会正确吗? 由于新机制不使用 GCD 并且异步任务和线程之间没有像以前那样的映射?
例如,我使用 SwiftUI List
refreshable
List { }
.refreshable {
viewModel.fetchData()
}
这个可以吗
List { }
.refreshable {
DispatchQueue.main.async {
viewModel.fetchData()
}
}
或者我需要在 ViewModel class 上添加@MainActor? 我没有在项目中使用 async/await,因此仅将 MainActor 用于此单个可刷新似乎是多余的,我也不知道添加此类属性如何影响 ViewModel class 的其余方法和属性,他们现在使用组合。
但另一方面 Xcode 显示
运行时:SwiftUI:不允许从后台线程发布更改; 确保在 model 更新上发布来自主线程的值(通过接收(on :) 等运算符)。
此外,在将@MainActor 添加到 ViewModel 之后,我收到了多个这样的警告
与全局参与者“MainActor”隔离的属性“标题”不能满足协议“OnlineBankingListViewModelProtocol”的相应要求
您问:
我还能使用
DispatchQueue.main.async
吗?
如果您在async
方法中并希望将某些内容分派到主队列,则最直接的等价物是:
MainActor.run { ... }
但更谨慎的做法是简单地用@MainActor
标记方法(或其类)。 这不仅可以确保它在主线程上运行,而且如果您尝试从错误的参与者调用它,您会收到编译时警告。
因此,如果您的视图模型标有@MainActor
,则无需在MainActor
上手动运行任务。 在处理观察对象的已发布属性时尤其如此。
例如,考虑:
@MainActor
class ViewModel: ObservableObject {
@Published var values: [Int] = []
func fetchData() async {
let foo = await ...
values = foo.values
}
}
接着
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
List {
...
}
.refreshable {
await viewModel.fetchData()
}
}
}
(注意,我将fetchData
async
方法并在refreshable
的范围内await
它,以便微调器准确反映async
进程何时运行。)
请参阅 WWDC 2021 视频Swift 并发:更新示例应用程序。 无可否认,这说明了 UIKit 应用程序的过渡,但包括@MainActor
和MainActor.run
的示例。
请注意,虽然@MainActor
很大程度上消除了对MainActor.run { … }
的需要,但仍有一些场景您可能会使用此run
模式。 具体来说,如果您在其他某个actor上并且想要在主线程上连续运行三个独立的@MainActor
函数,您可以将它们的系列包装在一个MainActor.run { … }
块中,从而运行所有三个对主要参与者的一次调度,而不是三个单独的调用。
上面,我专注于突出部分,但这是我的完整 MCVE:
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
List {
ForEach(viewModel.values, id: \.self) { value in
Text("\(value)")
}
}
.refreshable {
await viewModel.fetchData()
}
}
}
struct Foo: Decodable{
let json: [Int]
}
@MainActor
class ViewModel: ObservableObject {
@Published var values: [Int] = []
func fetchData() async {
do {
let foo = try await object(Foo.self, for: request)
values = foo.json
} catch {
print(error)
}
}
func object<T: Decodable>(_ type: T.Type, for request: URLRequest) async throws -> T {
let (data, response) = try await URLSession.shared.data(for: request)
guard let response = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard 200 ... 299 ~= response.statusCode else {
throw ApiError.failure(response.statusCode, data)
}
return try JSONDecoder().decode(T.self, from: data)
}
var request: URLRequest = {
let url = URL(string: "https://httpbin.org/anything")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "[1,2,3,4,5]".data(using: .utf8)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
return request
}()
}
enum ApiError: Error {
case failure(Int, Data)
}
在很多情况下,新的异步模型会找出最适合执行任务的线程,如果您的任务可以访问 Guide 对象,编译器/运行时将选择在主线程中运行整个任务,如果您如果你想在主线程中运行其他的东西,你可以使用@MainActor,但是你在主线程中强制的东西越多,你在那个线程上的工作就越多,其他线程的工作就越少,并且所以你的工作被分散在多个核心上的机会更少。 如果你真的推送它,你可以让一个任务在访问 Guide 的后台线程中运行,你会发现执行会跳转到主线程来执行 GUI 调用。 如果有一组调用你想在主线程中运行一些你编写的 gui 代码,只需将它们包装在一个任务中,运行时系统就会为你排序。
DispatchQueue.main.async { foo.bar() }
的替换是:
Task { @MainActor in
print(Thread.current.isMainThread) // "true"
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.