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
答案 0 :(得分:2)
What appears to be happening is:
migratePersistentStore
call.migratePersistentStore
call. Your backupTheStore
method will implicitly remove the original persistent store (as part of the migrate call) but it tries to fix things up by removing the new persistent store and re-adding the old one.The problem, I think, is that although you have re-added the persistent store used to fetch those managed objects, your migration process has lost the connection from the managed objects to the store. The migrate call clears out the persistent store coordinator state, which breaks the managed object / persistent store connection, and adding the persistent store doesn't re-create that connection. (Maybe it should but that's apparently not how it's designed).
As a result, you have managed objects that the persistent store coordinator can't relate to a persistent store file, and you crash when you try to use them.
Reloading the table view isn't enough because it will just reload the same managed objects from the fetched results controller. You should also make sure to call performFetch
on the fetched results controller to make it re-fetch its data. If that's not enough, set myFetchedResultsController
to nil and then reload the table, so that you'll get a completely new fetch.
答案 1 :(得分:0)
新的重要数据 - 这不仅仅是备份和还原问题。这个应用程序是一个iCloud应用程序,所以当然原始商店应该在本地ubiquity容器而不是app文档目录。我上面的代码确实适用于非iCloud设置。我已将originalStoreURL调整为指向本地ubiquity容器,现在我可以恢复原始数据存储。
正如Tom在上面指出的那样,通过引用应用程序文档目录来恢复商店,我实际上创建了一个新的商店,它在每个备份过程后都会保留 - 但这是一个本地的非iCloud商店。 / p>