【问题标题】:CoreData: Get notified when NSManagedObject is changed without keeping reference to NSManagedObjectCoreData:在 NSManagedObject 更改时收到通知,而不保留对 NSManagedObject 的引用
【发布时间】:2017-10-21 11:03:37
【问题描述】:

我想观察特定 NSManagedObject 的变化并相应地更新 UI。

不想保留对 NSManagedObject 的引用,因为它可能随时被删除(即通过远程推送通知的结果)。

目前我正在设置NSFetchRequestNSFetchedResultsControllerNSFetchedResultsControllerDelegate 来实现这一目标。但想简化这个解决方案(见下文)。

有没有不使用NSFetchedResultsControllerDelegate的简单方法来观察NSManagedObject的变化?

谢谢!

示例代码(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")

【问题讨论】:

  • NSFetchedResultsController 是一个简单的解决方案。我不清楚你想要它有多简单。我看起来你已经有了一个可行的解决方案,你的问题是什么?
  • 谢谢!我想要的 - 与现有解决方案一样工作的解决方案,但代码更少。

标签: ios macos core-data


【解决方案1】:

一种方法是:

  1. 保存您要观看的托管对象的objectID 属性的值,而不是托管对象的裁判。
  2. 使用NotificationCenterNSManagedObjectContextObjectsDidChange 通知添加观察者,该通知由您的托管对象上下文生成。
  3. 当您收到此通知时,请在 userInfo 字典中查找名为 NSUpdatedObjectsKey 的键。它包含对已更改的任何托管对象的引用。查看其中是否有人拥有您在第 1 步中保存的 objectID

根据您希望的工作方式,您可能更喜欢使用NSManagedObjectContextDidSave 通知。您可能还想使用NSInsertedObjectsKey 和/或NSDeletedObjectsKey

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-27
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多