ios - 核心数据迁移后主 TableView 崩溃 Swift

标签 ios core-data

我有一个相当标准的核心数据馈送 tableView,其中单元格数据从 fetchedResultsController 填充。

在我执行核心数据迁移之前,一切都按预期工作。轻量级迁移的目的是提供一个 简单备份不要更改模型。该商店使用 SQLite。计划是进行迁移以生成新的 数据文件,然后删除新存储并安装原始存储以保留原始文件名。

备份过程的 View 也是一个tableView。迁移完成后,新文件可见 在备份 TableView 中。单击“返回”按钮返回到原始tableView后,数据为 按预期可见,但单击 tableView 中的任何行会导致立即崩溃,我会看到 可怕的“无法从此 NSManagedObjectContext 的协调器访问对象的持久存储”错误。

我已经为此苦苦挣扎了一个星期。我一定是错过了一个基本概念。任何帮助,将不胜感激。 (iOS 8, Xcode 6.4)

这里是 fetchedResultsController 变量。同样,这些工作一直有效,直到进行迁移:

var myFetchedResultsController: NSFetchedResultsController? = nil

var fetchedResultsController: NSFetchedResultsController {

    managedObjectContext = kAppDelegate.managedObjectContext

    if myFetchedResultsController != nil {
        return myFetchedResultsController!
    }//if my ! nil

    let fetchRequest = NSFetchRequest()

    let entity = NSEntityDescription.entityForName("Patient", inManagedObjectContext: managedObjectContext)
    fetchRequest.entity = entity
    fetchRequest.fetchBatchSize = 50

    //Sort keys
    let sortDescriptor = NSSortDescriptor(key: "dateEntered", ascending: false)
    let sortDescriptors = [sortDescriptor]

    fetchRequest.sortDescriptors = [sortDescriptor]

    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)

    var countError : NSError? = nil
    var count = managedObjectContext.countForFetchRequest(fetchRequest, error: &countError)

    println("The count is \(count)")
    //after creating a backup, this count is ALWAYS zero - never the real count

    aFetchedResultsController.delegate = self
    myFetchedResultsController = aFetchedResultsController

    var error: NSError? = nil
    if !myFetchedResultsController!.performFetch(&error) {
         // Don't forget the code to handle the error appropriately.
         println("Unresolved error \(error), \(error!.userInfo)")
        //Remove this
         abort()
    }//if !my

    return myFetchedResultsController!
}//var fetchedResultsController

备份程序的两个函数:

func createLocalBackupFile() {

    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyyMMddHHmmss"
    let theDateTime = NSDate()
    let formattedDateTime = dateFormatter.stringFromDate(theDateTime)
    let backupFileName : String = "BiopBak" + formattedDateTime + ".sqlite"
    println("backupFileName is \(backupFileName)")

    let psu : CRSPersistentStoreUtilities = CRSPersistentStoreUtilities()//the function below is in this class

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
        //println("In a background queue, creating the backup file")
        psu.backupTheStore(backupFileName)

        //go back to the main queue
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            println("Back on main queue after creating the backup file")

            if (self.backupSqlFiles.count == 1 && self.backupSqlFiles[0] == "Placeholder for empty list") {
                self.backupSqlFiles.append(backupFileName.stringByDeletingPathExtension)
                self.backupSqlFiles.removeAtIndex(0)

            } else {
                self.backupSqlFiles.append(backupFileName.stringByDeletingPathExtension)

            }//if placeholder is only record in database - else

            self.tableView.reloadData()
            println("backupSqlFiles[] = \(self.backupSqlFiles)")

        })//back to main block - inner
    })//background processing block - outer

}//createLocalBackupFile

func backupTheStore(newSQLFileName : String) -> NSPersistentStore? {

        let storeType = NSSQLiteStoreType
        var migrateError : NSError?
        var currentStore : NSPersistentStore = kAppDelegate.persistentStoreCoordinator?.persistentStores.last! as! NSPersistentStore
        let options = [NSMigratePersistentStoresAutomaticallyOption: true,
            NSInferMappingModelAutomaticallyOption: true]

        let fileManager = NSFileManager.defaultManager()
        let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
        let docsDir = paths[0] as! String
        let docsDirURL = NSURL(fileURLWithPath: docsDir)

        let originalStoreURL : NSURL = docsDirURL?.URLByAppendingPathComponent("BiopLogCloud.sqlite") as NSURL!
        var newStoreURL : NSURL = docsDirURL?.URLByAppendingPathComponent(newSQLFileName) as NSURL!

    kAppDelegate.persistentStoreCoordinator?.migratePersistentStore(currentStore, toURL: newStoreURL, options: options, withType: storeType, error: &migrateError)

    currentStore = kAppDelegate.persistentStoreCoordinator?.persistentStores.last! as! NSPersistentStore

    var removeStoreError : NSError?
    var theStores = kAppDelegate.persistentStoreCoordinator?.persistentStores

    if let theStores2 = theStores {
        for removeStore in theStores2 {
            var removed : Bool = true
            kAppDelegate.persistentStoreCoordinator?.removePersistentStore(removeStore as! NSPersistentStore, error: &removeStoreError)

            if (removeStoreError != nil) {
                println("Unable to remove persistent store \(removeStore)")
            }
        }//for in
    }//if let theStores

    var addStoreError : NSError?

    kAppDelegate.persistentStoreCoordinator?.addPersistentStoreWithType(storeType,
        configuration: nil,
        URL: originalStoreURL,
        options: options,
        error:&addStoreError)

    if (addStoreError != nil) {
        println("Unable to add persistent store \(originalStoreURL)")
        //change this to add a user alert
    }//if

    //this does not seem to do any good
    let ptvc : PatientTableViewController = PatientTableViewController()

    dispatch_async(dispatch_get_main_queue()) {
        () -> Void in
        ptvc.tableView.reloadData()
    }//block

    return thisStore

}//backupTheStore

最佳答案

似乎正在发生的事情是:

  1. 您正在 TableView 中显示一些托管对象,这些对象是在 migratePersistentStore 调用之前获取的。
  2. 您执行 migratePersistentStore 调用。您的 backupTheStore 方法将隐式删除原始持久存储(作为迁移调用的一部分),但它会尝试通过删除新的持久存储并重新添加旧的来修复问题。
  3. 然后您尝试使用第 1 步中的一个托管对象。

我认为,问题是尽管您重新添加了用于获取那些托管对象的持久存储,但是您的迁移过程已经失去了从托管对象到存储的连接。迁移调用清除持久存储协调器状态,这会中断托管对象/持久存储连接,并且添加持久存储不会重新创建该连接。 (也许它应该但显然不是它的设计方式)。

因此,您管理的对象无法与持久存储协调器关联到持久存储文件,并且当您尝试使用它们时会崩溃。

重新加载 TableView 是不够的,因为它只会从获取的结果 Controller 重新加载相同的托管对象。您还应该确保在获取的结果 Controller 上调用 performFetch 以使其重新获取其数据。如果这还不够,将 myFetchedResultsController 设置为 nil 并然后重新加载表,这样您将获得全新的提取。

关于ios - 核心数据迁移后主 TableView 崩溃 Swift,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31754094/

相关文章:

ios - 自定义 UITableViewCell 中的所有元素变为 nil

ios - IOS 跨应用共享数据

swift - Swift:避免在CoreData中创建重复条目的最佳方法

ios - 如何使用 Swift 4 的 MagicalRecord 正确获取数据?

iphone - 如何从 iPhone 向 Windows 发送消息?

ios - swift 解决问题

ios - 如何在 iOS 7 中解析这个 xml?

ios - Swift 3 核心数据问题 - 所有数据丢失

iphone - 核心数据保存崩溃

core-data - 在一对多 CoreData 关系错误上调用 'count' 是否会将集合中的所有对象带入内存?