Search code examples
ioscore-datansmanagedobjectnsmanagedobjectcontext

Core Data Contexts Merge ( objects that have been created in child context)


I have Main context with Persistent Container, and Persistent Edit Context (a child of Main context), and Private Sync Context for long-running synchronisation.

Then I Create and object inside Edit Context -> Save it to Main Context -> Send these changes to server -> Receive updated changes from server -> Update edit context

I use

context.automaticallyMergesChangesFromParent = true

But I also tried merging manually.

@objc private func contextSave(_ notification: Notification) {
        let updated = (notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>)?
            .filter { $0 as? Order != nil }
            .map { $0 as! Order }

        updated?.forEach { order in
            Logger.shared.log("UPDATED FROM NOTIFICATION \n\n----\n\(order.objectID) \(order)\n-----\n\n")
        }

        localManagedContext.performAndWait {
            let orders = self.fetchInChildContext()
            orders.forEach {
                Logger.shared.log("LOCAL AFTER NOTIFICATION \n\n----\n\($0.objectID) \($0)\n-----\n\n")
            }
        }
    }

The result: objects that I have created on edit context and updated from synchronisation process are not updated EVEN though they are updated in Main Context and are inside NSUpdatedObjects set.

All other objects are merged correctly. And when objects are fetched from main context (and not created in child queue).

This is the object inside Edit Context

Optional(<BestatterprogrammI.Order: 0x600003f05720> (entity: Order; id: 0x600001cbefc0 <x-coredata:///Order/tBADA027E-D83F-4E34-A741-2EACD7877EE82> ; data: {

This is the object that I'm receiving inside NSUpdatedObjects

0xa8dc7e0d815e4391 <x-coredata://57464ABC-24B6-4E34-A992-3B2886E13686/Order/p2361> <BestatterprogrammI.Order: 0x600003ffec10> (entity: Order; id: 0xa8dc7e0d815e4391 <x-coredata://57464ABC-24B6-4E34-A992-3B2886E13686/Order/p2361> ; data: {

I'm saving Edit Context and Sync Context accordingly: // Edit Context (Main queue) localManagedContext.performAndWait { self.repopulateEntitiesID()

        do {
            if self.localManagedContext.hasChanges {
                try self.localManagedContext.save()

                if saveParent {
                    // using .parent on context results
                    // in unexpected behaviour on iOS 10
                    try CoreDataHandler.shared.persistentContainer.viewContext.save()
                }

                callback(.valid)
            } else {
                callback(.valid)
            }
        }


  // SYNC context (private queue)
  if context.hasChanges {
        do {
            try context.save()
        } catch let error {
            Logger.shared.logConsole(error)
        }
                   CoreDataHandler.shared.persistentContainer.viewContext.performAndWait {
            if CoreDataHandler.shared.persistentContainer.viewContext.hasChanges {
                do {
                    try CoreDataHandler.shared.persistentContainer.viewContext.save()
                    self.onSyncEventRaised.forEach { $0.value() }
                } catch let error {
                    Logger.shared.logConsole(error)
                }
            }
        }
    }

Please point me to an error and how can I make context up-to-date (not changing the context).

Refreshing objects result in unexpected behaviour, where these object duplicate inside context.

What I want is to update created objects in Edit Context and allow user to edit it furthermore. Thanks in advance.

UPD: Creating sync context (temporary) :

 let syncContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
 syncContext.parent = CoreDataHandler.shared.persistentContainer.viewContext
 syncContext.perform {

Creating edit context (persistent) :

let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = CoreDataHandler.shared.persistentContainer.viewContext
context.retainsRegisteredObjects = true
context.automaticallyMergesChangesFromParent = true

Solution

  • Influenced by: Core Data: Do child contexts ever get permanent objectIDs for newly inserted objects?

    Same problem appeared here: Core Data merge two Managed Object Context

    Basically what I had - the object that was created inside Edit context had a TEMPORARY id, but the object that was inside Main Context had a permanent ID.

    So Core Data thought it was two different objects so it wouldn't be merged. Solution to that - give a permanent ID to inserted objects before save.

     localManagedContext.performAndWait {
            self.repopulateEntitiesID()
    
            do {
                if self.localManagedContext.hasChanges {
                    // all inserted objects must get permanent ID to be updated
                    // from other contexts before fetching it from persistent store directly
    
                    let insertedObjects = Array(self.localManagedContext.insertedObjects)
                    try self.localManagedContext.obtainPermanentIDs(for: insertedObjects)
                    try self.localManagedContext.save()
    
                    if saveParent {
                        try CoreDataHandler.shared.persistentContainer.viewContext.save()
                    }
    
                    callback(.valid)
                } else {
                    callback(.valid)
                }
            }
            catch let error {
                Logger.shared.logConsole(error)
                callback(.error)
            }
        }