CoreData:更改NSManagedObject时得到通知,而无需保留对NSManagedObject的引用 [英] CoreData: Get notified when NSManagedObject is changed without keeping reference to NSManagedObject

查看:74
本文介绍了CoreData:更改NSManagedObject时得到通知,而无需保留对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")


推荐答案

一种方法将是:


  1. 保存您所管理对象的 objectID 属性的值

  2. 使用 NotificationCenter NSManagedObjectContextObjectsDidChange 通知,该通知由您的托管对象上下文生成。

  3. 收到此通知时,请查看 userInfo 字典中找到名为 NSUpdatedObjectsKey 的键。它包含对任何已更改的托管对象的引用。查看其中是否有您在步骤1中保存的 objectID

  1. Save the value of the objectID property of the managed object you want to watch, instead of a referee to the managed object.
  2. Use NotificationCenter to add an observer for the NSManagedObjectContextObjectsDidChange notification, which is generated by your managed object context.
  3. When you receive this notification, look at the userInfo dictionary for a key called NSUpdatedObjectsKey. It contains references to any managed objects that have changed. See if any of them have the objectID 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆