繁体   English   中英

Swift async/await 它替换 DispatchQueue.main.async

[英]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 应用程序的过渡,但包括@MainActorMainActor.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM