mckamike mckamike - 6 months ago 32
iOS Question

Errors rebuilding/resetting Core Data

I am trying to rebuild the core data data stack if a lightweight migration failed, and send the user back to the login screen. I am testing this by changing a to-many relationship to a to-one.

At first, I used the same URL (storeURL) when adding the new persistentStoreCoordinator after removing it; however, I got an error stating "Can't add the same store twice" in rebuildCoreData() at the line "try persistentStoreCoordinator.add..."

Secondly, I decided to change the url in the new persistent store by appending a "1" so it became self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData1.sqlite"). This made a step in the right direction - no errors yet, and I am able to land back onto the login screen. However, after trying to make the first save after logging in, I get an error 'This NSPersistentStoreCoordinator has no persistent stores (schema mismatch or migration failure). It cannot perform a save operation.'

What am I doing wrong here?

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
let options = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: options)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason

dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
self.rebuildCoreData()
}

return coordinator
}()

lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
managedObjectContext.mergePolicy = NSRollbackMergePolicy //This policy discards in-memory state changes for objects in conflict. The persistent store’s version of the objects’ state is used

return managedObjectContext
}()

// MARK: - Tearing down core data stack and rebuilding it in the case that a lightweight migration fails
func rebuildCoreData() {

let storeURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
do {
try NSFileManager.defaultManager().removeItemAtURL(storeURL)
} catch {
print(error)
abort()
}

for object in managedObjectContext.registeredObjects {
managedObjectContext.deleteObject(object)
}

do {
try persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true])
} catch {
print(error)
abort()
}

print("successfully rebuilt core data")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()
self.window?.rootViewController?.presentViewController(controller!, animated: false, completion: nil)
}


UPDATE - Changed file deletion and edited persistent store coordinator logic in the catch block

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
var coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
let options = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: options)
} catch {

// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason

dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")

//rebuilds core data
coordinator = self.rebuildCoreData(coordinator)
}

return coordinator
}()


New rebuildCoreData code:

// MARK: - Tearing down core data stack and rebuilding it in the case that a lightweight migration fails
func rebuildCoreData(coordinator: NSPersistentStoreCoordinator) -> NSPersistentStoreCoordinator {

let persistentStoreParentPath = self.applicationDocumentsDirectory.path
let fileEnumerator = NSFileManager.defaultManager().enumeratorAtPath(persistentStoreParentPath!)
while let path = fileEnumerator?.nextObject() as? String {
if path.hasPrefix("SingleViewCoreData.sqlite") || path.hasPrefix(".SingleViewCoreData.sqlite") {
let pathToDelete = (persistentStoreParentPath! as NSString).stringByAppendingPathComponent(path)
do {
try NSFileManager.defaultManager().removeItemAtPath(pathToDelete)
}
catch _ {
// Handle error removing file
}
}
}

for object in managedObjectContext.registeredObjects {
managedObjectContext.deleteObject(object)
}

do {
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true])
} catch {
print(error)
abort()
}

print("successfully rebuilt core data")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()
self.window?.rootViewController?.presentViewController(controller!, animated: false, completion: nil)

return coordinator
}

Answer

The CoreData stack does not consist of a single file in more recent iOS versions. You can create your CoreData stack and inspect the filesystem to see specifically what the files are named (there's an additional .shm and a .wal file, as I recall). Having said that, when I clear out the files I typically use NSFileManager to enumerate the objects at the parent path of my store files and remove anything with the prefix <StoreFileName>.sqlite and .<StoreFileName>.sqlite. You could also consider putting your store files in a sub directory and then just delete the entire sub directory.

let persistentStoreParentPath = self.applicationDocumentsDirectory.path
let fileEnumerator = NSFileManager.defaultManager().enumeratorAtPath(persistentStoreParentPath)
while let path = fileEnumerator?.nextObject() as? String {
    if path.hasPrefix("SingleViewCoreData.sqlite") || path.hasPrefix(".SingleViewCoreData.sqlite") {
        let pathToDelete = (persistentStoreParentPath as NSString).stringByAppendingPathComponent(path)
        do {
            try NSFileManager.defaultManager().removeItemAtPath(pathToDelete)
        }
        catch _ {
            // Handle error removing file
        }
    }
}

Your code above has some other problems to consider as well, though. You're calling rebuildCoreData() from inside the closure that initializes persistentStoreCoordinator, but you use persistentStoreCoordinator in rebuildCoreData() both directly and indirectly (via accessing managedObjectContext, which uses persistentStoreCoordinator).

Comments