CoreData:更改NSManagedObject时得到通知,而无需保留对NSManagedObject的引用 [英] CoreData: Get notified when NSManagedObject is changed without keeping reference to NSManagedObject
问题描述
我想观察特定的 NSManagedObject
的变化并相应地更新UI。
I want to observe changes of particular NSManagedObject
and update UI accordingly.
我不要'不想保留对 NSManagedObject
的引用,因为它可能随时被删除(即通过远程推送通知的结果)。
I don't want to keep reference to NSManagedObject
because it might be removed at any time (i.e. by result of remote push notification).
此刻,我正在设置 NSFetchRequest
, NSFetchedResultsController
和 NSFetchedResultsControllerDelegate
即可实现。但是要简化此解决方案(请参见下文)。
At the moment I'm setting up NSFetchRequest
, NSFetchedResultsController
and NSFetchedResultsControllerDelegate
to achieve this. But want to simplify this solution (see below).
是否有任何简单的方法来观察 NSManagedObject
中的更改而无需使用 NSFetchedResultsControllerDelegate
?
Is there any simple way to observe changes in NSManagedObject
without using NSFetchedResultsControllerDelegate
?
谢谢!
示例代码(Xcode Playground)
Sample code (Xcode Playground)
import PlaygroundSupport
import Cocoa
import CoreData
PlaygroundPage.current.needsIndefiniteExecution = true
extension NSManagedObject {
public static var entityName: String {
let className = String(describing: self)
return className.components(separatedBy: ".").last!
}
public convenience init(in context: NSManagedObjectContext) throws {
let entityName = type(of: self).entityName
guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else {
fatalError()
}
self.init(entity: entityDescription, insertInto: context)
}
}
@objc(UserInfoEntity)
class UserInfoEntity: NSManagedObject {
@NSManaged var id: Int64
@NSManaged var name: String
convenience init(id: Int64, name: String, in context: NSManagedObjectContext) throws {
try self.init(in: context)
self.id = id
self.name = name
}
}
class DBStack {
static let shared = DBStack()
static var mainContext: NSManagedObjectContext {
return shared.mainContext
}
private typealias PSC = NSPersistentStoreCoordinator
private lazy var coordinator: PSC = PSC(managedObjectModel: self.model)
private lazy var model: NSManagedObjectModel = self.setupModel()
private lazy var writerContext: NSManagedObjectContext = self.setupWriterContext()
private lazy var mainContext: NSManagedObjectContext = self.setupMainContext()
private var isInitialized = false
init() {
}
func setupInMemoryStore() throws {
guard !isInitialized else { return }
isInitialized = true
try coordinator.addPersistentStore(ofType: NSInMemoryStoreType,
configurationName: nil, at: nil, options: nil)
}
static func makeChildContext() -> NSManagedObjectContext {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.parent = mainContext
return moc
}
private func setupWriterContext() -> NSManagedObjectContext {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.persistentStoreCoordinator = coordinator
return moc
}
private func setupMainContext() -> NSManagedObjectContext {
let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
moc.parent = writerContext
return moc
}
private func setupModel() -> NSManagedObjectModel {
let attributeID = NSAttributeDescription()
attributeID.name = "id"
attributeID.attributeType = .integer64AttributeType
attributeID.isOptional = false
attributeID.isIndexed = true
let attributeName = NSAttributeDescription()
attributeName.name = "name"
attributeName.attributeType = .stringAttributeType
attributeName.isOptional = false
let entityUserInfo = NSEntityDescription()
entityUserInfo.name = "UserInfoEntity"
entityUserInfo.managedObjectClassName = "UserInfoEntity"
entityUserInfo.properties = [attributeID, attributeName]
let model = NSManagedObjectModel()
model.entities = [entityUserInfo]
return model
}
}
class FetchedResultsDelegate: NSObject, NSFetchedResultsControllerDelegate {
public var entityChanged: ((Void) -> Void)?
public func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
entityChanged?() // Notify about content change.
}
}
// Create or Update user info.
func updateUserInfo(id: Int64, name: String) {
let privateContext = DBStack.makeChildContext()
privateContext.perform {
let request: NSFetchRequest<UserInfoEntity> = NSFetchRequest(entityName: UserInfoEntity.entityName)
request.predicate = NSPredicate(format: "%K == %@", argumentArray: [#keyPath(UserInfoEntity.id), id])
request.fetchLimit = 1
do {
if let userInfo = try privateContext.fetch(request).first {
userInfo.name = name
} else {
_ = try UserInfoEntity(id: id, name: name, in: privateContext)
}
if privateContext.hasChanges {
print("→ Will save userInfo. Name: " + name)
try privateContext.save()
}
} catch {
print(error)
}
}
}
let stack = DBStack()
try stack.setupInMemoryStore()
let userID: Int64 = 1
let request: NSFetchRequest<UserInfoEntity> = NSFetchRequest(entityName: UserInfoEntity.entityName)
request.predicate = NSPredicate(format: "%K == %@", argumentArray: [#keyPath(UserInfoEntity.id), userID])
request.sortDescriptors = [NSSortDescriptor(key: #keyPath(UserInfoEntity.name), ascending: false)]
let delegate = FetchedResultsDelegate()
let fetchedResultsController: NSFetchedResultsController<UserInfoEntity>
= NSFetchedResultsController(fetchRequest: request, managedObjectContext: DBStack.mainContext,
sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = delegate
// Here is our event handler. Called on main thread.
delegate.entityChanged = { [weak fetchedResultsController] in
let userInfo = fetchedResultsController?.fetchedObjects?.first
print("! UserInfo changed: \(String(describing: userInfo?.name))")
// Update UI.
}
try fetchedResultsController.performFetch()
DispatchQueue.global().async {
updateUserInfo(id: userID, name: "Alex")
updateUserInfo(id: userID, name: "Alexander")
}
将打印:
→ Will save userInfo. Name: Alex
! UserInfo changed: Optional("Alex")
→ Will save userInfo. Name: Alexander
! UserInfo changed: Optional("Alexander")
推荐答案
一种方法将是:
- 保存您所管理对象的
objectID
属性的值 - 使用
NotificationCenter
为NSManagedObjectContextObjectsDidChange
通知,该通知由您的托管对象上下文生成。 - 收到此通知时,请查看
userInfo
字典中找到名为NSUpdatedObjectsKey
的键。它包含对任何已更改的托管对象的引用。查看其中是否有您在步骤1中保存的objectID
。
- Save the value of the
objectID
property of the managed object you want to watch, instead of a referee to the managed object. - Use
NotificationCenter
to add an observer for theNSManagedObjectContextObjectsDidChange
notification, which is generated by your managed object context. - When you receive this notification, look at the
userInfo
dictionary for a key calledNSUpdatedObjectsKey
. It contains references to any managed objects that have changed. See if any of them have theobjectID
you saved in step 1.
根据您希望事情如何工作,您可能更喜欢使用 NSManagedObjectContextDidSave
通知。您可能还想使用 NSInsertedObjectsKey
和/或 NSDeletedObjectsKey
。
Depending on how you want things to work, you might prefer to use the NSManagedObjectContextDidSave
notification instead. You might also want to use NSInsertedObjectsKey
and/or NSDeletedObjectsKey
.
这篇关于CoreData:更改NSManagedObject时得到通知,而无需保留对NSManagedObject的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!