0

I have the following CoreData stack in my app:

let mom = NSManagedObjectModel(contentsOf:modelURL)!
managedObjectContext = NSManagedObjectContext(concurrencyType:.mainQueueConcurrencyType)
privateContext = NSManagedObjectContext(concurrencyType:.privateQueueConcurrencyType)
coordinator = NSPersistentStoreCoordinator(managedObjectModel:mom)
managedObjectContext.automaticallyMergesChangesFromParent = true
privateContext.automaticallyMergesChangesFromParent = true
privateContext.persistentStoreCoordinator = coordinator
managedObjectContext.parent = privateContext
managedObjectContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
privateContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)

So, managedObjectContext is what I use for Main Actor's reads/writes, while the privateContext is responsible for heavy lifting – writing/reading from SQL store itself.

When I need to save data, I use the following method:

func save() {
    if !(managedObjectContext.hasChanges) {
        return
    }
    do {
        try self.managedObjectContext.safeSave()
        do {
            try self.privateContext.safeSave()
        }
        catch let error {
            // HANDLE ERROR
        }
    }
    catch let error as NSError {
        // HANDLE ERROR
    }
}

// .....

extension NSManagedObjectContext {
    func safeSave() throws {
        try performAndWait {
            if hasChanges {
                try save()
            }
        }
    }
}

The problem that I encounter is that whenever I save an entity, any SwiftUI view which observes it gets updated twice. And the reason for a second update is that due to automaticallyMergesChangesFromParent set to true, the second update is when an entity turns into a fault.

This is extremely confusing and I do not understand why this occurs. For example:

struct CellView: View {
    @ObservedObject private var topic: TopicStatistic

    var body: some View {
        print("CELL BODY \(topic)")
        return Button {
            topic.isSelected = !topic.isSelected
            DataStack.shared.save() //calls save method illustrated above
        } label: { Text("Test") }
    }
}

This prints two "CELL BODY" whenever I tap on the button, the first one is:

CELL BODY <Topic: 0x600002137b60> (entity: Topic; id: 0x85da4595cd988912 <x-coredata://F05EF0BE-58F4-423C-A12E-E4FF67BE17D5/Topic/p65>; data: {
    isSelected = 0;
    // other properties here
})

The second one is:

CELL BODY <Topic: 0x60000213dc20> (entity: Topic; id: 0x8db98711ee4d8ece <x-coredata://F05EF0BE-58F4-423C-A12E-E4FF67BE17D5/Topic/p53>; data: <fault>)

As you can see, the second update is when Topic turns into a fault.

What I have verified:

  1. Removing any one of automaticallyMergesChangesFromParent removes this issue
  2. If I do not use the "middle" privateContext and attach managedObjectContext directly to the persistent store, the issue also resolves.

However, none if these are good options for me. I have another background context which is used for Cloud sync, so I do want to both keep this setup and to ensure that changes are propagated.

Does anyone know why objects turn into faults when I would assume that changes go one way to the persistent store when I click on a button?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.