Основные данные. Как поменять местами 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 ответ
Мне удалось собрать ответ из нескольких ответов на похожие вопросы, в частности, этот от Тома Харрингтона. Короче говоря, мне нужно было сделать следующее:
- Создать новый
NSPersistentContainer
, - Замените текущее постоянное хранилище постоянным хранилищем восстановления / резервного копирования, используя
replacePersistentStore
метод наpersistentStoreCoordinator
, - Уничтожьте хранилище резервных копий.
- Удалите файлы, связанные с хранилищем резервных копий.
- Восстановить
Core Data
укладывать вAppDelegate
, - Сохраните, обнулите и затем повторно инициализируйте 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)
}
}