【发布时间】:2021-08-12 10:31:22
【问题描述】:
有什么方法可以告诉 SwiftUI 接收对主线程上 @ObservedObject 属性的更改?
我有一个模型对象,用于管理聊天频道中的聊天消息和用户列表。该通道由一些 websocket 代码提供,这些代码在我的模型上定期调用 addMessages() 或 usersJoinedAndLeft() 等方法。然后我的模型对用户和消息列表进行一些内务管理。
class ChatStream
@Published var users: [Users]
@Published var messages: [Message]
}
我的 SwiftUI 看起来像
struct OverlayChatView : View {
@ObservedObject public var stream: ChatStream
var body: some View {
…
ForEach(self.stream.messages) { inMsg in
}
…
}
}
这一切都很好。当我的网络代码更新以在不同的调度队列上进行调用时,问题就出现了。现在.users 和.messages 的更改发生在此队列上,SwiftUI 尝试在此队列上更新。
显而易见的解决方法是将我对.users 和.messages 的所有更改封装在DispatchQueue.main.async 调用中。但这有点混乱,因为所有的内务处理都是就地完成的,可能会在完成处理一组传入消息或用户状态更改之前更新 .users 和 .messages 几次(最后它会保留一个有序列表消息以及当前和离开用户的集合,每条消息都引用其作者,因为用户可以回来,他们的属性可以改变。)
因此,我可以更改我的代码以处理这些集合的临时副本,并且仅在调度异步调用中的处理结束时设置已发布的属性。这将具有合并许多临时更改的额外好处。
我可以添加第二个 PassthroughSubject 并在最后调用 .send(),但这看起来很笨拙,并且会破坏 SwiftUI 的精简特性,因为我可能必须保留一份单独的状态副本才能更新主线程。
我喜欢做的事情是这样的
@ObservedObject(receiveOn: DispatchQueue.main) var stream: ChatStream
或者在我的模型中,类似
class ChatStream {
func add(messages: [Message]) {
self.$users.suspend()
self.$messages.suspend()
…process messages…
DispatchQueue.main.async {
self.$users.resume()
self.$messages.resume()
}
}
但 SwiftUI 似乎应该只为我在主线程上重建 UI,因为它需要它。
【问题讨论】:
-
如果您使用组合发布者来更新您的消息/用户名,您可以使用此修饰符切换到主 Runloop developer.apple.com/documentation/combine/fail/…
-
我不确定我是否理解您关于使用
DispatchQueue.main.async会变得混乱的原因 -
因为对模型的每次修改都会生成通知。我的模型负责更新列表中的每个用户,如果他们离开了,将他们移动到单独的列表中,更新消息列表并对它们进行排序,等等。我都在原地完成了这些。我必须在主线程上进行整个处理,这是我想避免的。
-
@Rick,“因为对模型的每次修改都会生成通知”——这是不正确的。 Published 属性的编辑不会每次都导致重新呈现。重新渲染被合并。
-
这不是我所看到的。特别是,无论是否合并,它们都不会进入主线程。