Базовые данные. Как поменять местами NSPersistentStores и сообщить NSFetchedResultsController?

Я выполняю резервное копирование и восстановление (через Dropbox) Core Data сохраненных данных пользователя. Для восстановления я извлекаю файлы из Dropbox и временно сохраняю их в каталоге «Документы». Затем я создаю новый NSPersistentContainer и использую его для замены текущего постоянного хранилища (в каталоге ApplicationSupport) перед удалением ненужных больше файлов в каталоге Documents.

Сейчас я использую шаблон MasterDetail, поэтому у меня есть обычные объекты с отметками времени и NSFetchedResultsController, которые идут вместе с ними. Сделав замену, я возвращаюсь к основному tableView и, к сожалению, вижу исходный набор сущностей. Когда приложение перезапускается, я вижу восстановленные данные, как и предполагалось, но мне нужно решить, как заставить NSFetchedResultsController видеть новые восстановленные данные автоматически.

Это мой код восстановления:

func restorePSFromBackup() {
    // Current persistent store is in the ApplicationSupport directory.
    let currentPSFolderUrl = FileManager.default.urls(for: .applicationSupportDirectory, in:.userDomainMask).first!
    // The current Core Data file (url).
    let currentPSUrl1 = currentPSFolderUrl.appendingPathComponent("DBCDTest.sqlite")
    // Backup persistent store with which to restore is in the Documents directory.
    let backUpFolderUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    // The 3 backup Core Data files (urls).
    let backupUrl1 = backUpFolderUrl.appendingPathComponent("DBCDTest.sqlite")
    let backupUrl2 = backUpFolderUrl.appendingPathComponent("DBCDTest.sqlite-wal")
    let backupUrl3 = backUpFolderUrl.appendingPathComponent("DBCDTest.sqlite-shm")
    let sourceSqliteURLs = [backupUrl1, backupUrl2, backupUrl3]

    let container = NSPersistentContainer(name: "DBCDTest")

    do {
        // Replace current persistent store with the restore/backup persistent store.
        try container.persistentStoreCoordinator.replacePersistentStore(at: currentPSUrl1, destinationOptions: nil, withPersistentStoreFrom: backupUrl1, sourceOptions: nil, ofType: NSSQLiteStoreType)
        // Delete the restore/backup files from the Application directory.
        do {
            for index in 0..<sourceSqliteURLs.count {
                try FileManager.default.removeItem(at: sourceSqliteURLs[index])
            }
        } catch let error {
            print("Failed to delete sqlite files.")
            print(error.localizedDescription)
        }
    } catch let error {
        print("Failed to replace persistent store.")
        print(error.localizedDescription)
    }
}

Вместо создания нового NSPersistentContainer я попытался использовать внедрение зависимостей, чтобы сослаться на тот, который был создан в AppDelegate, и использовать его для выполнения свопа, но попытка заменить постоянные хранилища не удалась. Может быть, это какая-то проблема NSManagedObjectContext? Может ли он нуждаться в очистке перед доступом к новому хранилищу данных?


person Magnas    schedule 01.06.2018    source источник


Ответы (1)


Мне удалось собрать ответ из нескольких ответов на похожие вопросы, в частности, этот из Том Харрингтон. Вкратце, что мне нужно было сделать:

  1. Создайте новый NSPersistentContainer.
  2. Замените текущее постоянное хранилище на постоянное хранилище для восстановления/резервного копирования, используя метод replacePersistentStore для файла persistentStoreCoordinator.
  3. Уничтожить хранилище резервных копий.
  4. Удалите файлы, связанные с хранилищем резервных копий.
  5. Перестройте стек Core Data в файле AppDelegate.
  6. Сохраните, обнулите, а затем повторно инициализируйте managedObjectContext и NSFetchedResultsController MasterViewController.

Номеру 6 потребовалось некоторое время, чтобы я увидел свет. Мой окончательный метод восстановления:

func restorePSFromBackup() {
    // Current persistent store is in the ApplicationSupport directory.
    let currentPSFolderUrl = FileManager.default.urls(for: .applicationSupportDirectory, in:.userDomainMask).first!
    // The current Core Data file (url).
    let currentPSUrl1 = currentPSFolderUrl.appendingPathComponent("DBCDTest.sqlite")
    // Backup persistent store with which to restore is in the Documents directory.
    let backUpFolderUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    // The 3 backup Core Data files (urls).
    let backupUrl1 = backUpFolderUrl.appendingPathComponent("DBCDTest.sqlite")
    let backupUrl2 = backUpFolderUrl.appendingPathComponent("DBCDTest.sqlite-wal")
    let backupUrl3 = backUpFolderUrl.appendingPathComponent("DBCDTest.sqlite-shm")
    let sourceSqliteURLs = [backupUrl1, backupUrl2, backupUrl3]

    let container = NSPersistentContainer(name: "DBCDTest")

    do {
        // Replace current persistent store with the restore/backup persistent store.
        try container.persistentStoreCoordinator.replacePersistentStore(at: currentPSUrl1, destinationOptions: nil, withPersistentStoreFrom: backupUrl1, sourceOptions: nil, ofType: NSSQLiteStoreType)
        // Destroy the backup store.
        try container.persistentStoreCoordinator.destroyPersistentStore(at: backupUrl1, ofType: NSSQLiteStoreType, options: nil)
        // Delete the restore/backup files from the Application directory.
        do {
            for index in 0..<sourceSqliteURLs.count {
                try FileManager.default.removeItem(at: sourceSqliteURLs[index])
            }
        } catch let error {
            print("Failed to delete sqlite files.")
            print(error.localizedDescription)
        }
        // Rebuild the AppDelegate's Core Data stack.
        (UIApplication.shared.delegate as! AppDelegate).persistentContainer = NSPersistentContainer(name: "DBCDTest")
        (UIApplication.shared.delegate as! AppDelegate).persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
            print(NSPersistentContainer.defaultDirectoryURL())
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            } else {
                // Save, nil and then reinitialise the MasterViewController managedObjectContext and NSFetchedResultsController.
                do {
                    try self.masterViewController.managedObjectContext?.save()
                } catch let error {
                    print("Failed to save managedObjectContext.")
                    print(error.localizedDescription)
                }
                self.masterViewController.managedObjectContext = nil
                self.masterViewController.managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
                self.masterViewController._fetchedResultsController = nil
                let _ = self.masterViewController.fetchedResultsController
            }
        })
    } catch let error {
        print("Failed to replace persistent store.")
        print(error.localizedDescription)
    }
}
person Magnas    schedule 04.06.2018