核心数据。如何交换 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 的 managedObjectContextNSFetchedResultsController

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?

Day770.Redis客户端如何与服务器端交换命令和数据 -Redis 核心技术与实战

计网基础-数据交换之电路交换

计算机网络5--网络核心之数据交换基础及电路交换

三层楼100人办公网络如何规划设计实施(实战案例)

计算机网络1.3 网络核心的数据交换