Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
937 views
in Technique[技术] by (71.8m points)

core data - NSManagedObject fetched into 2 different contexts has different attribute values

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?

question from:https://stackoverflow.com/questions/65871404/nsmanagedobject-fetched-into-2-different-contexts-has-different-attribute-values

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...