在 Core Data 中,您可以执行类似的操作来观察某个类型的所有对象的属性变化:
.onReceive(NotificationCenter.default
.publisher(for: .NSManagedObjectContextObjectsDidChange)
) { notif in
guard let userInfo = notif.userInfo else { return }
let updatedItems: [Item] = (userInfo[NSUpdatedObjectsKey] as?
Set<NSManagedObject> ?? [])
.compactMap{$0 as? Item}
let becameActive = updatedItems.filter{ (item: Item) -> Bool in
guard let activeBefore = item
.changedValuesForCurrentEvent()[#keyPath(Item.active)]
as? Bool else { return false }
return !activeBefore && item.active
}
}
如何在 SwiftData 中实现这一点?该事件仍然会触发,但
compactMap{$0 as? Item}
始终返回 nil。我想这是因为在 SwiftData 中 Item
是一个 PersistentModel,而不是 NSManagedObject 子类。更清洁的解决方案可加分。
我发现私人确实更改通知:
extension ModelContext {
public static let didChangeX = NSNotification.Name(rawValue: "_SwiftDataModelsChangedInContextNotificationPrivate")
}
但是,我在拆箱私有
AnyPersistentObject
类型时遇到了困难,以便能够在我的应用程序中获取真实类型。但由于我不需要对象的实际属性,只需要它的类型,所以我想出了一个解决方法来比较包含该类型的字符串:
public var modelContext: ModelContext! {
didSet {
if modelContext == oldValue { return } // perhaps should be removed, to have same semantics as the current var fetchDescriptor
_results = nil
NotificationCenter.default.removeObserver(self)
if let modelContext {
NotificationCenter.default.addObserver(self,
selector: #selector(contextModelsChanged),
name: ModelContext.didChangeX,
object: modelContext)
}
}
}
@objc private func contextModelsChanged(_ notification: Notification) {
guard let userInfo = notification.userInfo else { return }
// since AnyPersistentObject is private we need to use string comparison of the types
let search = "AnyPersistentObject(boxed: \(String(reflecting: T.self)))" // e.g. AppName.Item
for key in ["updated", "inserted", "deleted"] {
if let set = userInfo[key] as? Set<AnyHashable> {
if set.contains(where: { String(describing: $0) == search }) {
withTransaction(transaction) {
_results = nil
}
return
}
}
}
}
仅供参考,此代码来自我的 SwiftDataX 项目,在该项目中,我实现了
NSFetchedResultsController
等效项,并将其用于 @DynamicQuery
,这是一个类似于 @Query
的属性包装器,但允许动态配置获取描述符(如排序和谓词)。