I want to observe changes of particular NSManagedObject
and update UI accordingly.
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).
At the moment I'm setting up NSFetchRequest
, NSFetchedResultsController
and NSFetchedResultsControllerDelegate
to achieve this. But want to simplify this solution (see below).
Is there any simple way to observe changes in NSManagedObject
without using NSFetchedResultsControllerDelegate
?
Thank you!
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 print:
→ Will save userInfo. Name: Alex
! UserInfo changed: Optional("Alex")
→ Will save userInfo. Name: Alexander
! UserInfo changed: Optional("Alexander")
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…