核心数据。如何交换 NSPersistentStores 并通知 NSFetchedResultsController?
Posted
技术标签:
【中文标题】核心数据。如何交换 NSPersistentStores 并通知 NSFetchedResultsController?【英文标题】:Core Data. How to swap NSPersistentStores and inform NSFetchedResultsController? 【发布时间】:2018-06-01 16:23:27 【问题描述】:我正在对用户的 Core Data
持久数据实施备份和恢复(通过 Dropbox)。为了恢复,我从 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】:我已经设法从许多类似问题的答案中总结出一个答案,尤其是来自Tom Harrington 的这个答案。简而言之,我需要做的是:
-
创建一个新的
NSPersistentContainer
。
使用persistentStoreCoordinator
上的replacePersistentStore
方法将当前持久存储替换为恢复/备份持久存储。
销毁备份存储。
删除与备份存储关联的文件。
重建AppDelegate
中的Core Data
堆栈。
保存,nil 然后重新初始化 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)
【讨论】:
以上是关于核心数据。如何交换 NSPersistentStores 并通知 NSFetchedResultsController?的主要内容,如果未能解决你的问题,请参考以下文章
核心数据。如何交换 NSPersistentStores 并通知 NSFetchedResultsController?