SwiftUI - 确保在模型更新时从主线程发布值(通过像 receive(on:) 这样的运算符)

Cod*_*ght 31 swiftui combine

我的应用程序包含一个资源密集型操作,该操作根据从 XML 提要中提取的数据填充数组。我不希望此操作锁定主线程(以及为数组提供新数据时的 UI),因此它是在后台完成的。

let dispatchQueue = DispatchQueue(label: "concurrent.queue", qos: .utility, attributes: .concurrent)

class XMLHandler: ObservableObject {
    let context: NSManagedObjectContext

    @Published var myArray: [CustomObject] = []

    init(context: NSManagedObjectContext) {
        self.context = context
    }
    
    ...some code...
    
    func populateArray {
      dispatchQueue.async {
       ...xml parsing happens...
       (xmlOutputObject) in 
          for x in xmlOutputObject {
              self.myArray.append(x)
          }
      }
    }
}
Run Code Online (Sandbox Code Playgroud)

在其他地方,我的 SwiftUI 视图使用 myArray 来填充它的列表:

struct MyView: View {

 @EnvironmentObject var handler: XMLHandler
    var body: some View {
            List{
                ForEach(handler.myArray) { CustomObject in
              ... generate rows ...
                    }
                }
            }
Run Code Online (Sandbox Code Playgroud)

当我的应用程序尝试更新 @Published var myArray: [CustomObject] = [] 时,会出现运行时错误。

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

我知道这与采用合并有关,但老实说我不知道​​从哪里开始。任何帮助,将不胜感激。

我只是希望发生以下情况:

  1. 用户按下按钮启动填充 myArray 的 XML 数据拉取
  2. myArray 填充在后台线程上,保持 UI 响应
  3. MyView 中的列表会在任务完成后自动更新。目前,我必须离开视图并再次返回才能刷新列表。

小智 46

只需放在@MainActor定义应该在主线程中运行的类之前即可。使用新的 Swift 并发性非常简单。

@MainActor class DocumentsViewModel: ObservableObject { ... }
Run Code Online (Sandbox Code Playgroud)

在新的 WWDC 2021 视频或类似文章中,有很多相关信息:Using the MainActor attribute toautomatic split UI updates on the main queue

  • 这是否意味着“@MainActor”中发生的所有事情都将在主线程上完成?甚至异步调用? (8认同)
  • 添加“@MainActor”后,我继续在同一位置收到相同的错误。 (6认同)

Rob*_*ier 16

由于追加发生在循环中,因此您需要决定是要为每个项目发出一次新值,还是为整个更新发出一次新值。如果您不确定,请在整个更新过程中更新一次。

然后在主队列上执行该操作:

  ...xml parsing happens...
   (xmlOutputObject) in 
      DispatchQueue.main.async {   // <====
          self.append(contentsOf: xmlOutputObject)
      }
Run Code Online (Sandbox Code Playgroud)

关键点是您无法读取或写入多个队列上的属性。在本例中,您要使用的队列是主要队列(因为它驱动 UI)。因此,您必须确保所有属性访问都发生在该队列上。


Max*_*eod 13

iOS 期望所有 UI 更改都在主线程上进行。SwiftUI 也不例外。在主线程外执行 UI相关工作是可以接受的,但这些更改迟早需要带到主线程上进行渲染。

\n

\xe2\x80\x99t 似乎您正在使用合并。但是,如果你当时出现这个错误;

\n
\n

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

\n
\n

最好使用建议的组合调度运算符来解决:

\n
.receive(on: DispatchQueue.main)\n
Run Code Online (Sandbox Code Playgroud)\n

在 Apple\xe2\x80\x99s 中使用调度运算符在调度队列之间移动工作使用合并处理 URL 会话数据任务结果一文中对此进行了详细介绍:

\n
cancellable = urlSession\n    .dataTaskPublisher(for: url)\n    .receive(on: DispatchQueue.main)\n    .sink(receiveCompletion: { print ("Received completion: \\($0).") },\n          receiveValue: { print ("Received data: \\($0.data).")})\n
Run Code Online (Sandbox Code Playgroud)\n


Dav*_*ski 12

DispatchQueue.main.async {
    nc.post(name: Notification.Name(NotificationStrings.AccountPostReceived), object: nil)
}
Run Code Online (Sandbox Code Playgroud)

  • 说明:从主线程发布值 (4认同)
  • 除了代码之外,请解释您的解决方案。 (3认同)