【问题标题】:Realm Write Transaction without Notifying Any Observers in SwiftUI App在 SwiftUI 应用程序中不通知任何观察者的领域写入事务
【发布时间】:2021-08-22 00:53:43
【问题描述】:

我想在我的 Realm 数据库上执行某些写入操作,而无需通知整个 SwiftUI 应用程序中的任何观察者。原因是我正在将我的 Realm 数据库同步到 CloudKit,当 CloudKit 响应记录已成功更新时,我设置了 Realm 对象的 modified 属性以匹配服务器。我不希望由此触发通知,因为这是用户已经在 UI 中进行的更改,并且同步往返不应中断 UI。

由于我的观察者遍布我的应用程序中的各种ObservableObjects,我将所有令牌合并到我在应用程序首次启动时初始化的单例中。

class RealmSetup{
  static let shared = RealmSetup()
  
  var tokenAlpha: NotificationToken? = nil
  var tokenBravo: NotificationToken? = nil
}

然后他们像这样在其他地方设置:

class AlphaModel: ObservableObject{
  static let shared = AlphModel()

  init(){
    let realm = try! Realm()
    RealmSetup.shared.tokenAlpha = realm.objects(Alpha.self).observe { [weak self] _ in
      self?.objectWillChange.send()
    }
  }
}

...同样:

class BravoModel: ObservableObject{
  static let shared = BravoModel()

  init(){
    let realm = try! Realm()
    RealmSetup.shared.tokenBravo = realm.objects(Bravo.self).observe { [weak self] _ in
      self?.objectWillChange.send()
    }
  }
}

这是我写作的精髓:

let realm = try! Realm()
realm.beginWrite()

//Do a bunch of record changes...

//Find the non-nil notification tokens and try not to notify them
var tokensReady = [NotificationToken]()

for token in [RealmSetup.shared.tokenAlpha, RealmSetup.shared.tokenBravo]{
  if let ready = token{
    tokensReady.append(ready)
  }
}

try! realm.commitWrite(withoutNotifying: tokensReady)

我遇到的问题是,当同步完成并尝试提交写入时,我遇到了崩溃:

不正确的领域:只能跳过正在修改的领域的通知。

显然,令牌与写入不在同一个领域。据我所知,我的 ObservableObjects 位于主线程上,但如果我将编写代码包装在 DispatchQueue.main.async { ... } 中,我仍然会崩溃。

我不是线程方面的专家,所以我有点困惑。我怎样才能确保所有这些事情都发生在同一个线程上?还是有更简单的方法来做我想做的事情?

【问题讨论】:

  • Realm 可以在不通知观察者的情况下写入数据。见write(withoutNotifying:_:)。我很好奇为什么两个独立的数据库? Realm 和 iCloud (CloudKit),Realm 什么时候可以处理所有这些?您介意分享一下用例吗?
  • 是的,我在我的问题中提到了withoutNotifying 选项并解释了相关的崩溃。我使用 CloudKit 是因为我已经熟悉它了,它是免费的、稳定的,而且 API 不需要在我的应用程序中进行身份验证。由于潜在的成本,我对使用 MongoDB Realm 同步感到紧张,并且过度依赖单一供应商(我过去一直被这种情况所困扰)。
  • 我明白了。好吧,如果您想专注于一个解决方案,请放心,Realm 可以处理整个事情。我提到了 withoutNotifying,因为它确实有效——我们一直在使用它。代码通常看起来不错 - 尽管 ObservableObject 与 ObservedRealmObject 可能存在问题。

标签: realm


【解决方案1】:

我会将您的服务器状态存储在一个单独的对象中并创建一个LinkingObjects 属性:

class Something: Object {
    @objc dynamic var name: String = ""
    @objc dynamic var someValue: String = ""

    let status = LinkingObjects<SomethingServerStatus>(
        fromType: SomethingServerStatus.self,
        property: "object"
    )

    override class func primaryKey() -> String? {
        return "name"
    }

    // ...
}


class SomethingServerStatus: Object {
    @objc dynamic var object: Something? = nil
    @objc dynamic var modified: Bool = false

    // ...
}

确保将服务器状态链接到这样的对象:

let objects = [
    Something(name: "zero"),
    Something(name: "one"),
    Something(name: "two"),
]
.map {
    SomethingServerStatus(object: $0)
}

try! realm.write {
    realm.add(objects)
}

照常观察:

    let realm = try! Realm()
    self.objects = realm.objects(Something.self)
    notificationToken = self.objects.observe { change in
        switch change {
        case .update(_, _, _, let modifications):
            // do something ...
        default:
            break
        }
    }

当您需要更新服务器状态时,只需获取和修改。这不会触发任何侦听器的通知:

    let realm = try! Realm()
    try! realm.write {
        realm.objects(SomethingServerStatus.self)
            .filter("object.name == %@", someName)
            .first?
            .modified
            .toggle()
    }

【讨论】:

  • 这太棒了!谢谢!我认为它可以更简单,并且仍然具有不收到虚假通知的预期效果。我将发布另一个答案来说明我的意思,但我会将你的答案标记为已接受,因为这个想法很棒。
【解决方案2】:

在@Rob 的回答的推动下,我想到了使用稍微简单的对象关系。使用他的例子,我可以这样做:

//Example of a synced object class
class Something: Object {
  @objc dynamic var name: String = ""
  @objc dynamic var someValue: String = ""
  @objc dynamic var status: Status

  override class func primaryKey() -> String? {
    return "name"
  }
}

//Sync status class
class Status: Object {
  @objc dynamic var modified = false
}

然后当我的同步往返完成时,我可以更新相关对象:

something.status.modified = Date()

...Something 对象的通知不会触发,因为更改是在相关对象上进行的。谢谢,罗伯!

【讨论】:

  • 您确定更改状态不会导致有关某事的通知吗?我认为会的。
  • 我很确定它不会,因为我有其他关系,我必须想办法任意更改相关对象,以便为它们的父级触发更改。但我可能是错的。
  • 在尝试了这个之后,我发现一个缺点是必须将我的 Realm 数据库中的对象数量增加一倍,以便跟踪它们的修改和更改状态。对于每个同步的常规对象,我都需要一个 Status 对象。 ?
  • 这个解决方案肯定会在父对象上触发通知。这是 Realm 与 Core Data 不同的一种方式。关于将领域中的对象加倍,我不认为这是一个问题。但是,我可以想办法通过实现单个 Status 类而不是为需要同步的每个 Class 单独的 Status 类来减少类的数量。
猜你喜欢
  • 2018-04-29
  • 1970-01-01
  • 2019-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多