使用 Combine 和 SwiftUI 在 Realm 中观察收集结果

Posted

技术标签:

【中文标题】使用 Combine 和 SwiftUI 在 Realm 中观察收集结果【英文标题】:Observe Collection Results in Realm with Combine and SwiftUI 【发布时间】:2021-02-05 00:37:21 【问题描述】:

我正在尝试this quick start for SwiftUI and Combine,以尝试了解如何将我的 Realm 数据库连接到 Combine。

该示例观察到RealmSwift.List 并保留一个填充了其数据的表。这是一个指向子类的链表。我想知道如何观察 Results 集合,以便跟踪整个 Realm 类的任何更改。

例如,假设我有一个Workspace 类:

class Workspace: Object, ObjectKeyIdentifiable
  @objc dynamic var id = UUID().uuidString
  @objc dynamic var name = ""
  @objc dynamic var archived = false

在状态对象中,我可以像这样设置一个Results<Workspace>变量:

class AppState: ObservableObject 
  @Published var workspaces: Results<Workspace>?
  var cancellables = Set<AnyCancellable>()
  
  init()

    let realmPublisher = PassthroughSubject<Realm, Error>()

    realmPublisher
      .sink(receiveCompletion:  _ in , receiveValue:  realm in
        //Get the Results
        self.workspaces = realm.objects(Workspace.self)
      )
      .store(in: &cancellables)

    realmPublisher.send(try! Realm())

    return
  

但是当需要观察对象时,我不能,因为Results 不是对象(我假设)。

struct ContentView: App 
  @ObservedObject var state = AppState()
  var view: some View 
    ItemsView(workspaces: state.workspaces!)  
  
  var body: some Scene 
    WindowGroup 
      view.environmentObject(state)
    
  


struct ItemsView: View 
  @ObservedObject var workspaces: Results<Workspace> //<!-- Error

  var body: some View 
    //...
  

Xcode 在workspaces 属性上给出语法错误:

属性类型“Results”与其包装类型“ObservedObject”的“wrappedValue”属性不匹配

是否可以观察一组Results,就像我们可以在Results 的集合上设置一个通知监听器一样?

【问题讨论】:

【参考方案1】:

从技术上讲,您可以将 sink 连接到 state.workspaces (state.$workspaces.sink()),但在这种情况下,我认为您将问题复杂化了。

您的ContentView (AppState) 中已经有一个@ObservableObject,它正在为您管理结果。因此,将ItemsView 更改为仅将其作为参数:

var workspaces: Results<Workspace>?

它不需要是@ObservedObject——无论是在那个视图中还是在它的父视图中,它都会被重新渲染。它在这里必须是可选的,因为它是 AppState 上的可选值,除非你想通过强制展开 (!) 继续传递它,但这通常是个坏主意,因为如果它会崩溃曾经事实上nil

此外,在您的Realm 代码中,请确保它与您所遵循的教程相匹配。例如,您有Publisher.sink,实际上应该是realmPublisher.sink

【讨论】:

非常感谢您的帮助。我是Combine的新手,所以我的理解非常有限。我会试试你的建议。我的代码中确实有realmPublisher,只是在我的问题中打错了字。我会解决的。再次感谢。 我尝试了你的建议。我在应用加载时获得初始数据,但没有收到更改通知。 AppState() 对象似乎没有传播其更改。据我了解,我需要将workspaces 设为@StateObject 才能将自己绑定到父@ObservedObject,但由于workspaces 不是对象,所以我不能。 它不是必须是 @StateObject —— 必须有其他事情发生。当您在接收器内打印工作区的内容时会发生什么? 看起来什么都没有发生。更新上没有打印任何内容(我直接在 Realm 数据库中进行更改以触发更改)。 嗯,这意味着不是组合部分不起作用——而是领域部分【参考方案2】:

你是对的,Results 是一个结构体,因此不能被@StateObject@ObservedObject 覆盖。您的解决方法现在适合。

https://github.com/realm/realm-cocoa/pull/7045 发布后,您将能够使用新的 Realm 属性包装器之一将您的 Results 直接嵌入到视图中。在此发布时,该名称将是 @FetchRealmResults,但可能会发生变化。

【讨论】:

这听起来正是我想要的。所以你会抽象出 Publisher 的东西,让我专注于在需要它们的地方获取 Results。我认为这类似于Core Data 对Combine 所做的事情。让我知道我需要为谁购买披萨才能发布 PR。 ?

以上是关于使用 Combine 和 SwiftUI 在 Realm 中观察收集结果的主要内容,如果未能解决你的问题,请参考以下文章

使用 Combine 和 SwiftUI 显示变化值的最简洁方式是啥?

使用 Combine 和 SwiftUI 在 Realm 中观察收集结果

如何使用 SwiftUI 和 Combine 检测 Datepicker 的值变化?

使用 Combine 和 SwiftUI 对点击的按钮进行更改

异步下载图像时SwiftUI和Combine工作不顺畅

使用 SwiftUI 和 Combine 根据授权状态有条件地显示视图?