#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 Я попробовал это в проекте с точно такими же настройками, как у вас, и это работает. Выдает ли это какую-либо ошибку?