2017-05-21 5 views
0

I Änderungen von besonderer NSManagedObject beobachten wollen und dementsprechend UI aktualisieren.Coredata: Mich benachrichtigen wenn NSManagedObject ohne Bezug auf NSManagedObject zu halten geändert wird

I wollen nicht zu behalten Bezug auf NSManagedObject, weil es jederzeit entfernt werden kann (d. H. Durch das Ergebnis der Remote-Push-Benachrichtigung).

Im Moment richte ich NSFetchRequest, NSFetchedResultsController und NSFetchedResultsControllerDelegate ein, um dies zu erreichen. Aber ich möchte diese Lösung vereinfachen (siehe unten).

Gibt es eine einfache Möglichkeit, Änderungen in ohne Verwendung von NSFetchedResultsControllerDelegate zu beobachten?

Vielen Dank!

Beispielcode (Xcode Spielplatz)

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") 
} 

Wird drucken:

→ Will save userInfo. Name: Alex 
! UserInfo changed: Optional("Alex") 
→ Will save userInfo. Name: Alexander 
! UserInfo changed: Optional("Alexander") 
+1

A 'NSFetchedResultsController' ist eine einfache Lösung. Ich bin nicht klar, wie viel einfacher du es willst. Ich sehe aus, als hättest du schon eine Lösung, die funktioniert, was ist deine Frage? –

+0

Danke! Was ich will - eine Lösung, die funktioniert wie die bestehende, aber mit weniger Code. – Vlad

Antwort

1

Eine Möglichkeit wäre:

  1. Speichern, um den Wert der objectID Eigenschaft des verwalteten Objekts Sie möchten anstelle eines Referenten auf das verwaltete Objekt achten.
  2. Verwenden NotificationCenter einen Beobachter für die NSManagedObjectContextObjectsDidChange Meldung hinzufügen, die von Ihnen verwalteten Objektkontext erzeugt wird.
  3. Wenn Sie diese Benachrichtigung erhalten, sehen Sie im userInfo Wörterbuch nach einem Schlüssel mit der Bezeichnung NSUpdatedObjectsKey. Es enthält Verweise auf alle verwalteten Objekte, die geändert wurden. Sehen Sie, wenn einer von ihnen haben die objectID Sie in Schritt gespeichert 1.

Je nachdem, wie Sie die Dinge arbeiten wollen, könnten Sie es vorziehen, die NSManagedObjectContextDidSave Benachrichtigung stattdessen zu verwenden. Vielleicht möchten Sie auch NSInsertedObjectsKey und/oder NSDeletedObjectsKey verwenden.

+0

Danke Tom! Ich habe es nie geschafft, 'NSManagedObjectContext'-Benachrichtigungen zu verwenden. Definiere Code kürzer als Version mit 'NSFetchedResultsController' (https://gist.github.com/vgorloff/2f84bca5917f53d7b7578f9436b4e151). Es ist auch möglich, Änderungen durch eine benutzerdefinierte eindeutige ID anstelle von "objectID" zu beobachten. – Vlad

Verwandte Themen