0

My app has a reproducible CoreData error.
I use the viewContext for display, and a backgroundContext for object updates. Both contexts belong to the same NSPersistentCloudKitContainer.
At some point, I save in the backgroundContext an object after updating its status attribute from 2 to 1, and its updatedAt attribute from nil to Date(). Later, I want to fetch back this updated object, and my understanding is that a fetch always returns the content of the persistent store.
Thus, the fetched object should be the same regardless into which context it is fetched. However, this is not the case.
I have also set -com.apple.CoreData.ConcurrencyDebug 1 as a launch argument, so this is not a CoreData multithreading error. Here is my test code:

The object is saved here:

let context = backgroundContext!
context.performAndWait {
    assert(ItemStatus(rawValue: item.status) == .isBought)
    item.status = ItemStatus.isToBuy.rawValue
    item.updatedAt = Date()
    _ = saveContext(context)
}  

with

func saveContext(_ context: NSManagedObjectContext) -> Error? {
    if !context.hasChanges { return nil }
    let inserts = context.insertedObjects; if !inserts.isEmpty { print("Will save inserted objects: \(inserts)") }
    let updates = context.updatedObjects;  if !updates.isEmpty { print("Will save updated objects: \(updates)") }
    let deletes = context.deletedObjects;  if !deletes.isEmpty { print("Will save deleted objects: \(deletes)") }
    do {
        try context.save()
        print("\(context.name!) saved")
    } catch {
        fatalError("Unresolved error")
    }
    return nil
}  

Later, I fetch the object into both contexts using:

let mwFetchRequest = NSFetchRequest<Item>(entityName: Item.entityName)
let passwordPredicate = NSPredicate(format: "\(Schema.Item.password) == %@", password)
let namePredicate = NSPredicate(format: "\(Schema.Item.name) == %@", "Mineral water")
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [passwordPredicate, namePredicate])
mwFetchRequest.predicate = compoundPredicate
mwFetchRequest.returnsObjectsAsFaults = false

backgroundContext.performAndWait {
    let bcItem = try! backgroundContext.fetch(mwFetchRequest)
    print("backgroundContext: \(bcItem)")
}

viewContext.performAndWait {
    let vcItem = try! viewContext.fetch(mwFetchRequest)
    print(„viewContext: \(vcItem)")
}  

And here is the log when I set a breakpoint after this code:

Will save updated objects: [<ShopEasy.Item: 0x600000d7cf50> (entity: Item; id: 0x9698d776a7665623 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Item/p1617>; data: {
    buyPlaces = "<relationship fault: 0x600002e296a0 'buyPlaces'>";
    fixedAtTopAt = nil;
    howOftenBought = 1;
    lastBoughtDate = "2021-01-24 13:02:09 +0000";
    name = "Mineral water";
    password = "PW_1";
    status = 1;
    updatedAt = "2021-01-24 13:32:14 +0000";
})]
backgroundContext saved
…
backgroundContext: [<ShopEasy.Item: 0x600000d7cf50> (entity: Item; id: 0x9698d776a7665623 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Item/p1617>; data: {
    buyPlaces = "<relationship fault: 0x600002e296a0 'buyPlaces'>";
    fixedAtTopAt = nil;
    howOftenBought = 1;
    lastBoughtDate = "2021-01-24 13:02:09 +0000";
    name = "Mineral water";
    password = "PW_1";
    status = 1;
    updatedAt = "2021-01-24 13:32:14 +0000";
})]
viewContext: [<ShopEasy.Item: 0x600000d75ae0> (entity: Item; id: 0x9698d776a7665623 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Item/p1617>; data: {
    buyPlaces =     (
        "0x9698d776b10e5621 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Place/p971>"
    );
    fixedAtTopAt = nil;
    howOftenBought = 1;
    lastBoughtDate = "2021-01-24 13:02:09 +0000";
    name = "Mineral water";
    password = "PW_1";
    status = 2;
    updatedAt = nil;
})]  

Obviously, the object is first correctly saved using the backgroundContext, and should thus be in the persistent store.
It is then fetched back correctly into the backgroundContext.
But after fetching the same object into the viewContext, the two changed attributes, status and updatedAt, have the values as they were before the save.

My questions:
Are my assumptions wrong? Is something wrong with my code?

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
  • 1
    I think your understanding is wrong - from the docs: "If you fetch some objects, work with them, and then execute a new fetch that includes a superset of those objects, you do not get new instances or update data for the existing objects—you get the existing objects with their current in-memory state." – pbasdf Jan 24 '21 at 19:51

1 Answers1

1

Later, I want to fetch back this updated object, and my understanding is that a fetch always returns the content of the persistent store.

The fetch selects the objects to return based on the content of the persistent store, but it does not by default update the in-memory copies of the objects based on the store’s content. There is an option to do this, which in my experience doesn’t work. To update an existing object from the store, you could refresh it or set up merging on your context so that changes to the store are automatically propagated.

Michael Tsai
  • 1,945
  • 17
  • 41