CoreData backgroundContext save doesn't save and is lost

半世苍凉 提交于 2021-02-11 17:39:39

问题


I'm having a problem saving things in the background. So I have 2 operations that I chain with RxSwift. The first items are saved and when I fetch them on a main context all the information is there. But in the second operation I fetch the same object and I update it with more information. Now whatever I do there it never updates the entity.

This is my coredata code:

class CoreDataStack {
    static let modelName = "Mo_Application"
    static let model: NSManagedObjectModel = {
        let modelURL = Bundle(for: CoreDataStack.self).url(forResource: modelName, withExtension: "momd")!
        return NSManagedObjectModel(contentsOf: modelURL)!
    }()

    lazy var mainContext: NSManagedObjectContext = {
        return self.storeContainer.viewContext
    }()

    lazy var storeContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: CoreDataStack.modelName, managedObjectModel: CoreDataStack.model)
        container.loadPersistentStores { (_, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        }
        return container
    }()

    func newDerivedContext() -> NSManagedObjectContext {
        return self.storeContainer.newBackgroundContext()
    }

    func saveContext() throws {
        try? self.saveContext(mainContext)
    }

    func saveAndWaitContext(_ context: NSManagedObjectContext) throws {
        if context != mainContext {
            try self.saveAndWaitDerivedContext(context)
            return
        }

        context.performAndWait {
            try? context.save()
        }
    }

    func saveAndWaitDerivedContext(_ context: NSManagedObjectContext) throws {
        context.performAndWait {
            try? context.save()
            try? self.saveAndWaitContext(self.mainContext)
        }
    }
}

What I do in the first operation:

let context = coreDataStack.newDerivedContext()

let object = Foo(context: context)
object.name = "Name 1"

try coreDataStack.saveAndWaitDerivedContext(context)
maybe(.success(self)) (RxSwift to complete)

In the second operation I first fetch the object:

let context = coreDataStack.newDerivedContext()
let fetchRequest: NSFetchRequest<Foo> = Foo.fetchCreatedBy(user: user)
let objects: [Foo] = try context.fetch(fetchRequest)
let object = objects.first { $0.id == self.id }
if let object = object {
    object.name = "Name updated"
}
try coreDataStack.saveAndWaitDerivedContext(context)
maybe(.success(self))

When I now do a fetch on my mainContext the old value is still there.

When I call my saveAndWaitDerivedContext the background context hasChanges. But when I then save the mainContext it doesn't have changes? Is that normal?

What am I doing wrong?


回答1:


From the documentation on NSPersistentContainer.newBackgroundContext():

This new context will be associated with the NSPersistentStoreCoordinator directly and is set to consume NSManagedObjectContextDidSave broadcasts automatically.

This gives a hint that your background context writes directly into the persistent store. Main context will not know about this if you don't merge the changes made in the background context into it.

You should listen to NSManagedObjectContextDidSave notification from the background context, and merge changes from it into the main context.

func saveAndWaitDerivedContext(_ context: NSManagedObjectContext) throws {
    let observer = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextDidSave, object: context, queue: .main) { (notification) in
        self.mainContext.mergeChanges(fromContextDidSave: notification)
    }
    context.performAndWait {
        try? context.save()    
    }
    NotificationCenter.default.removeObserver(observer)
}



回答2:


As the previous answer said, because newBackgroundContext() function of NSPersistentContainer returns a new context that will be associated with the NSPersistentStoreCoordinator directly, you can tell your main context to automaticaly merge changes saved to its persistent store by setting automaticallyMergesChangesFromParent property to true:

lazy var mainContext: NSManagedObjectContext = {
    self.storeContainer.viewContext.automaticallyMergesChangesFromParent = true
    return self.storeContainer.viewContext
}()

As the automaticallyMergesChangesFromParent documentation states:

A Boolean value that indicates whether the context automatically merges changes saved to its persistent store coordinator or parent context.

If the above code doesn't work, maybe you should set this property after NSPersistentContainer loads its persistent stores:

lazy var storeContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: CoreDataStack.modelName, managedObjectModel: CoreDataStack.model)
    container.loadPersistentStores { (_, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
    return container
}()


来源:https://stackoverflow.com/questions/63752475/coredata-backgroundcontext-save-doesnt-save-and-is-lost

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!