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

Я осуществляю резервное копирование и восстановление (через Dropbox) пользователя Core Data постоянные данные. Для восстановления я извлекаю файлы из Dropbox и временно сохраняю их в каталоге Documents. Затем я создаю новый 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 вопрос? Может ли потребоваться очистка перед доступом к новому хранилищу данных?

1 ответ

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

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

№ 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)
    }
}
Другие вопросы по тегам