Сохранение фонового текста CoreData не сохраняется и теряется

#ios #swift #core-data

#iOS #swift #core-data

Вопрос:

У меня проблема с сохранением данных в фоновом режиме. Итак, у меня есть 2 операции, которые я связываю с RxSwift. Первые элементы сохраняются, и когда я извлекаю их из основного контекста, вся информация находится там. Но во второй операции я извлекаю тот же объект и обновляю его дополнительной информацией. Теперь, что бы я там ни делал, он никогда не обновляет объект.

Это мой код coredata:

 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)
        }
    }
}
  

Что я делаю в первой операции:

 let context = coreDataStack.newDerivedContext()

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

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

Во второй операции я сначала извлекаю объект:

 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))
  

Когда я теперь выполняю выборку в моем mainContext, старое значение все еще существует.

Когда я вызываю свой saveAndWaitDerivedContext, фоновый контекст изменяется. Но когда я затем сохраняю mainContext, в нем нет изменений? Это нормально?

Что я делаю не так?

Ответ №1:

Из документации по NSPersistentContainer.newBackgroundContext() :

Этот новый контекст будет связан с NSPersistentStoreCoordinator напрямую и настроен на автоматическое использование NSManagedObjectContextDidSave трансляций.

Это дает подсказку о том, что ваш фоновый контекст записывается непосредственно в постоянное хранилище. Основной контекст не узнает об этом, если вы не объедините в него изменения, внесенные в фоновом контексте.

Вы должны прослушать NSManagedObjectContextDidSave уведомление из фонового контекста и объединить изменения из него в основной контекст.

 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)
}
  

Комментарии:

1. Попробую это. Я обнаружил, что когда я вношу изменения в блок performAndWait, все работает. Нам не разрешено вносить изменения в объекты за пределами блока выполнения?

2. > Нам не разрешено вносить изменения в объекты за пределами блока выполнения? < Да, если вы изменяете управляемые объекты, которые принадлежат типу параллелизма NSManagedObjectContext с .privateQueue (что верно для вашего фонового контекста), вы должны изменять MOs только внутри performBlock / performBlockAndWait блока.

Ответ №2:

Как говорилось в предыдущем ответе, поскольку newBackgroundContext() функция NSPersistentContainer возвращает новый контекст, который будет связан с NSPersistentStoreCoordinator напрямую, вы можете указать своему основному контексту автоматическое объединение изменений, сохраненных в его постоянном хранилище, установив для automaticallyMergesChangesFromParent свойства значение true :

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

Как указано в документации automaticallyMergesChangesFromParent:

Логическое значение, указывающее, автоматически ли контекст объединяет изменения, сохраненные в его координаторе постоянного хранилища или родительском контексте.

Если приведенный выше код не работает, возможно, вам следует установить это свойство после NSPersistentContainer загрузки его постоянных хранилищ:

 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
}()
  

Комментарии:

1. Это объединяет изменения из parentContext , а не из постоянного хранилища.

2. @EugeneDudnyk Я обновил свой ответ документацией automaticallyMergesChangesFromParent свойства.

3. @user1007522 Я попробовал это в проекте с точно такими же настройками, как у вас, и это работает. Выдает ли это какую-либо ошибку?