vendredi 31 juillet 2015

After Core Data Migration Main TableView Crashes Swift

I have a fairly standard Core Data fed tableView with cell data populated from a fetchedResultsController.

Everything works as expected until I do a Core Data migration. The purpose of the lightweight migration is to provide a simple backup not to change the model. The store uses SQLite. The plan is to do the migration to generate the new data files and then to remove the new store and install the original store in order to keep the original file names.

The view for the backup procedure is also a tableView. Once the migration is completed, the new file is visible in the backup tableView. Upon clicking the "back" button to return to the original tableView, the data is visible as expected, but clicking on any row in the tableView causes an immediate crash and I'm presented with the dreaded "Object's persistent store is not reachable from this NSManagedObjectContext's coordinator" error.

I've been struggling with this for a week. I must be missing a basic concept. Any help would be appreciated. (iOS 8, Xcode 6.4)

Here are the fetchedResultsController variables. Again these work all the time until a migration is made:

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

The two functions for the backup procedure:

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

Aucun commentaire:

Enregistrer un commentaire