问题
I'm implementing backup and restore (via Dropbox) of a user's Core Data
persisted data. For a restore I pull the files down from Dropbox and store them temporarily in the Documents directory. Then I create a new NSPersistentContainer
and use it to replace the current persistent store (in the ApplicationSupport directory) before deleting the no longer needed files in the Documents directory.
I'm using the MasterDetail template for now and so I have the usual time-stamped entities and the NSFetchedResultsController
that comes along with that. Having made the replacement I swap back to the master tableView and unfortunately see the original set of entities. When the app restarts I see the restored data as intended but need to work out how to make the NSFetchedResultsController
see the new restored data automatically.
This is my restore code:
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)
}
}
Instead of creating a new NSPersistentContainer
I have tried using dependency injection to reference the one created in the AppDelegate and using that to perform the swap but it fails trying to replace the persistent stores. Could it be some kind of NSManagedObjectContext
issue? Could it need flushing prior to accessing the new data store?
回答1:
I've managed to pull an answer together from a number of answers to similar questions, in particular this one from Tom Harrington. In short what I needed to do was:
- Create a new
NSPersistentContainer
. - Replace the current persistent store with the restore/backup persistent store using the
replacePersistentStore
method on thepersistentStoreCoordinator
. - Destroy the backup store.
- Delete the files associated with the backup store.
- Rebuild the
Core Data
stack in theAppDelegate
. - Save, nil and then reinitialise the MasterViewController's
managedObjectContext
andNSFetchedResultsController
.
Number 6 took some time for me to see the light. My final restore method is:
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)
}
}
来源:https://stackoverflow.com/questions/50647579/core-data-how-to-swap-nspersistentstores-and-inform-nsfetchedresultscontroller