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
What appears to be happening is:
You're displaying some managed objects in your table view, which were fetched before the migratePersistentStore call.
You do the 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.
You then try to use one of the managed objects from step 1.
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.
New important data - this is not just a backup and restore issue. This app is an iCloud app so of course the original store should be in the local ubiquity container not the app documents directory. My code above does indeed work for a non-iCloud setup. I've adjusted the originalStoreURL to point to the local ubiquity container and I can now restore the original data store.
As Tom pointed out above, by referring to the app documents directory for the restoration of the store, I was actually creating a new store which also persisted after every backup procedure - however that was a local, non-iCloud store.
Related
Is there a way, to make multiple NSBatchUpdateRequest calls executed within a DB transaction, so that either all DB rows is updated or none is updated (When exception thrown)?
The following code illustrate the problem.
func debug() {
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
let fetchRequest = NSTabInfo.fetchSortedRequest()
do {
var objectIDs: [NSManagedObjectID] = []
let nsTabInfos = try fetchRequest.execute()
//
// QUESTION: We are updating multiple rows of data directly in a persistent store.
// How can we ensure either all rows is updated, or none row is updated is exception
// happens in between?
//
for nsTabInfo in nsTabInfos {
let batchUpdateRequest = NSBatchUpdateRequest(entityName: "NSTabInfo")
batchUpdateRequest.predicate = NSPredicate(format: "self == %#", nsTabInfo.objectID)
batchUpdateRequest.propertiesToUpdate = ["name": nsTabInfo.name! + "XXX"]
batchUpdateRequest.resultType = .updatedObjectIDsResultType
let batchUpdateResult = try backgroundContext.execute(batchUpdateRequest) as? NSBatchUpdateResult
guard let batchUpdateResultX = batchUpdateResult else { return }
guard let managedObjectIDs = batchUpdateResultX.result else { return }
if let nsManagedObjectIDs = managedObjectIDs as? [NSManagedObjectID] {
objectIDs.append(contentsOf: nsManagedObjectIDs)
}
//
// Simulate some exception
// We notice the first row is updated & rest of the rows are unchanged.
// This leaves our data in inconsistent state.
//
throw "Custom error!!!"
}
if !objectIDs.isEmpty {
let changes = [NSUpdatedObjectsKey : objectIDs]
coreDataStack.mergeChanges(changes)
}
} catch {
backgroundContext.rollback()
error_log(error)
}
}
}
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "wenote")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return backgroundContext
}()
func mergeChanges(_ changes: [AnyHashable : Any]) {
NSManagedObjectContext.mergeChanges(
fromRemoteContextSave: changes,
into: [persistentContainer.viewContext, backgroundContext]
)
}
}
We write a demo code to illustrate the following
Performing NSBatchUpdateRequest multiple times within a loop.
An exception happens in between.
We wishes none of the row in persistent store is updated. However, a row is already updated before the exception thrown.
May I know what technique I can use, which is similar to SQLite transaction feature, so that either all rows is updated, or none of the row is updated when exception happens?
CoreData.framework doesn't open up SQLite level controls to the user, it provides you NSManagedObjectContext.
How does it work in a similar manner?
You pull as many objects in many as you need and do your changes on them.
When you are done with your changes, you do context.save().
In that way, you save all of your changes in one shot.
In all cases, pulling all objects in memory might not be possible or a good idea, so then you need to implement your own solution around how to send all of these changes to disk.
From the NSBatchUpdateRequest docs -
A request to Core Data to do a batch update of data in a persistent store without loading any data into memory.
When you execute this, you are doing the changes in store that you can't roll back. For a large data-set, you can do following -
Say you have to perform a series of updates (5 different steps) on 100k records as an operation.
Start in a background thread, pull objects in memory in batches of 1k at a time.
You can load 1k objects easily in memory, mutate them - go through all of your changes/steps one by one and save these changes on this batch. If this is successful, you move on to the next batch.
In case one intermediate step fails on a batch, you can then use either NSManagedObjectContext.rollback() or NSManagedObjectContext.reset() depending on your implementation.
Here's a popular SO post on the differences between the two in case official docs don't provide enough clarity.
I have an application that will sync with a server with data that can change daily. During the sync, I remove all the data for some entities and reload it with new data. I am using the following code:
func SyncronizeUserComments(theData : [[AnyHashable : Any]])
{
// Delete User Comments for this User and Connection
let commentRequest : NSFetchRequest<NSFetchRequestResult> = PT_UserComments.fetchRequest()
commentRequest.predicate = NSPredicate(format: "connection = %# AND user == %#", Global_CurrentConnection!, Global_CurrentUser!)
coreData.processDeleteRequest(request: commentRequest)
// ADD the Comments to CoreData
for index in 0..<theData.count {
let result : [AnyHashable : Any] = theData[index]
if let commentID = result["Comment_ID"] as? String, let commentText = result["Comment_Text"] as? String, let commentTitle = result["Comment_Title"] as? String
{
let newUserComment = PT_UserComments(context: coreData.persistentContainer.viewContext)
newUserComment.connection = Global_CurrentConnection
newUserComment.user = Global_CurrentUser
newUserComment.comment_ID = commentID
newUserComment.comment_Text = commentText
newUserComment.comment_Title = commentTitle
}
}
// Add the User Comments
print("Added New User Comments: \(theData.count)")
coreData.saveContext()
}
func processDeleteRequest(request : NSFetchRequest<NSFetchRequestResult>)
{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try coreData.persistentContainer.viewContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey : objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as Any as! [AnyHashable : Any], into: [coreData.persistentContainer.viewContext])
} catch {
fatalError("Fatal Error Deleting Data: \(error)")
}
coreData.saveContext()
}
When I call coreData.saveContext() I will get a Merge Conflict against the deleted data.
In reading about CoreData and the NSBatchDeleteRequest, this deletes at the SQL LITE level and bypasses the in memory cache.
The only way I have been able to get this to work is by setting:
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
Is this correct, or am I doing something wrong? I am also setting this merge policy in my saveContext() in the Core Data Stack.
I just spent hours debugging the same issue, hopefully this can help someone.
The problem is that NSManagedObjectContext.mergeChanges(fromRemoteContextSave:, into:) updates the managed object context but does not update the row cache version number of the deleted objects relationships to match the updated version number (Z_OPT) in the database file, causing a mismatch at time of the save.
If you're using NSErrorMergePolicyType this will cause the next save to fail, (or even a later one when the relationships become flagged for save), even though everything but the version numbers match. I've not seen this mentioned in the related docs or WWDC video, but I guess Apple assumed people would always pick a non-default merge policy.
So picking NSMergeByPropertyStoreTrumpMergePolicy solves it, as mentioned in the question, but you might not want this policy for all your save operations. To avoid that I ended up writing a custom merge policy that only resolves version mismatches. The code is below (this is untested Swift as I originally wrote in Obj-C, but should be equivalent):
//Configure the merge as below before saving
context.mergePolicy = AllowVersionMismatchMergePolicy(merge: .errorMergePolicyType)
//...
//The custom merge policy
class AllowVersionMismatchMergePolicy: NSMergePolicy {
override func resolve(optimisticLockingConflicts list: [NSMergeConflict]) throws {
do {
//if the default resolve worked leave it alone
return try super.resolve(optimisticLockingConflicts: list)
} catch {
//if any of the conflict is not a simple version mismatch (all other keys being equal), fail
let hasValueConflict = list.contains { conflict -> Bool in
//compare object and row cache
if let objectSnapshot = conflict.objectSnapshot as NSObject?,
let cachedSnapshot = conflict.cachedSnapshot as NSObject? {
return !objectSnapshot.isEqual(cachedSnapshot)
}
//compare row cache and database
if let cachedSnapshot = conflict.cachedSnapshot as NSObject?,
let persistedSnapshot = conflict.persistedSnapshot as NSObject? {
return !cachedSnapshot.isEqual(persistedSnapshot)
}
//never happens, see NSMergePolicy.h
return true
}
if hasValueConflict {
throw error
}
//Use store rollback merge policy to resolve all the version mismatches
return try NSMergePolicy.rollback.resolve(optimisticLockingConflicts: list)
}
}
}
I am facing issues with my app where if i create or delete a new object, then save the object within a different entity object, then go back and try to make a new object of the first entity type, my app will crash.
I can then reopen then app and make the object that crashed the app with no issue.
This is all being done via core data, there is an exercise, exercises are saved as a routine, then creating a new exercise after having created a routine will crash the app. Furthermore, deleting an exercise and a routine then trying to create a new one straight after will also crash the app
I have spend a long time reading around this and believe the likely cause is managed object context and wondered if creating it as a singleton was the solution? I set up the MoC by running the below in each VC's viewdidload:
func getMainContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
I then reference this VC level variable via .self wherever i need to reference the MoC to avoid clashes with creating further MoC within a VC.
I believed this should prevent issues as all core data work is linked to the shared MoC. However as documented above, there are still crashes occurring.
Below is a console print of the crash which hopefully will narrow down the source.
fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=134020 "(null)" UserInfo={NSAffectedObjectsErrorKey= (entity: UserExercise; id: 0x600000025060
The code block this is triggering off as an example as 1 location in the app it occurs is included below, to clarify this only occurs when i just deleted other objects, if i reloaded the app now this code would work and save just fine:
func createExercise() {
print("SAVE EXERCISE PRESSED")
if userExercise == nil {
print("SAVING THE NEW EXERCISE")
let newUserExercise = UserExercise(context: self.managedObjectContext!)
newUserExercise.name = userExerciseName.text
newUserExercise.sets = Int64(userSetsCount)
newUserExercise.reps = Int64(userRepsCount)
newUserExercise.dateCreated = NSDate()
newUserExercise.hasBeenTickedDone = false
} if self.associatedRoutineToAddTo != nil {
let fetchRequest: NSFetchRequest<UserRoutine> = UserRoutine.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %#", self.associatedRoutineToAddTo!)
do {
let existingUserRoutine = try self.managedObjectContext!.fetch(fetchRequest).first
print("RETRIVED ROUTINES ARRAY CONTAINING \(existingUserRoutine)")
existingUserRoutine?.addToUserexercises(newUserExercise)
print("EXERCISE SUCESSFULLY ADDED TO ROUTINE")
} catch {
print("Fetching Routine Failed")
}
} else if self.associatedRoutineToAddTo == nil {
print("THIS IS A FRESH EXERCISE WITHOUT A PARENT ROUTINE")
}
} else if let userExercise = userExercise {
print("UPDATING THE EXISTING EXERCISE")
userExercise.name = userExerciseName.text
userExercise.sets = Int64(userSetsCount)
userExercise.reps = Int64(userRepsCount)
}
do {
try self.managedObjectContext?.save()
print("THE EXERCISE HAS BEEN SAVED")
} catch {
fatalError("Failure to save context: \(error)")
}
The variable declarations are:
var managedObjectContext: NSManagedObjectContext!
var userExercise: UserExercise?
var associatedRoutineToAddTo : String?
var editingUserExerciseID: NSManagedObjectID?
var editingUserExercise: UserExercise?
I was receiving the "NSCocoaErrorDomain Code=134020 (null)" error because my new entity was not added to the proper CoreData Configuration.
I'm trying to do the following:
Tap a cell of a UITableView cell then segue to the next UIViewController and display the database results. But there are multiple persistent stores therefore the designated store is specified by the cell label text.
The question is: How to use the method persistentStore(for: url)? Or is there some else way to specify a persistent store for the fetchRequest?
Here is my code that is not working:
func wordFetchRequest() -> NSFetchRequest<Word> {
let fr = NSFetchRequest<Word>(entityName: "Word")
fr.fetchBatchSize = 100
// Assigning sort descriptors
let firstLetterSort = NSSortDescriptor(key: #keyPath(Word.firstLetter), ascending: true)
let spellSort = NSSortDescriptor(key: #keyPath(Word.spell), ascending: true)
fr.sortDescriptors = [firstLetterSort, spellSort]
// Get URL of the designated store to fetch
let libname = (AppDelegate.nameDict as NSDictionary).allKeys(for: nameToFetch).first!
// I'm not sure the following line: which file should I use? I've tried
//.sqlite, .sqlite-shm and .sqlite-wal but none worked.
let url = AppDelegate.coreDataStack.storeDirectory.appendingPathComponent("\(libname).sqlite-wal")
// Specify affected store for the fetch request
var pss = [NSPersistentStore]()
print(url)
// The following line fails:
if let ps = coreDataStack.psc.persistentStore(for: url) {
pss.append(ps)
} else {
}
fr.affectedStores = pss
print(fr.affectedStores ?? "No stores available.")
return fr
}
Any help will be much appreciated.
I had to deal with similar scenario where I had different persistent stores (to be specific one of type NSInMemoryStoreType and other of type NSSQLiteStoreType to be specific)
I found it easier to create separate persistent store coordinators for each store and create separate managed object context using these persistent stores as there parent stores :)
Here is the code which was written in iOS 9 swift 3, hence has older core data stack operations, I have seen iOS 10 Swift 3 Core data stack, I believe these methods can still give you idea of what am talking here :)
This is what you will see by default in Coredata stack, getter for persistentStoreCoordinator
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.appendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
log.debug(url)
} catch let error as NSError {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
catch{
}
return coordinator
}()
Important statement here though is
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
As you can see it specifies the persistent store type as Sqlite and specifies the configurationName as nil, which means default configuration :)
You can create multiple configurations in Coredata and specify there name in this statement to create separate persistent store coordinator for each configurations :)
You can have a look at my blog Can core data be trusted with sensitive informations to see how you can create multiple configurations and stores :)
So lets assume you create another configuration and added entities to them and lets call it as "Test1" configuration, you will create a separate persistent store coordinator for that using,
lazy var test1PersistentStoreCoordinator: 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.appendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: "Test1", at: url, options: nil)
log.debug(url)
} catch let error as NSError {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
catch{
}
return coordinator
}()
Now you have two persistent store coordinators associated with two different configurations, simply create two managed object contexts using these persistent store coordinators as their parent store :)
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
return managedObjectContext
}()
lazy var managedObjectContextForBackTracking : NSManagedObjectContext = {
let coordinator = self.test1PersistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Thats it :)
Now run your fetch requests on corresponding managedObject contexts :) and be sure that nothing messes up your core data :)
Hope it helps :)
I handle two complete Core Data stacks in my app:
The one that is provided by default in AppDelegate.
A second stack I fully create in order to perform NSManagedObject updates in a private queue, to avoid blocking the UI.
I have a class to create the second "auxiliary" Core Data stack, and I do this way:
class CoreDataStack: NSObject {
class func getPrivateContext() -> NSManagedObjectContext {
let bundle = NSBundle.mainBundle()
let modelURL = bundle.URLForResource("MyApp", withExtension: "momd")
let model = NSManagedObjectModel(contentsOfURL: modelURL!)!
let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
let privateContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = psc
let documentsURL = CoreDataStack.applicationDocumentsDirectory()
let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")
let options = [NSMigratePersistentStoresAutomaticallyOption: true]
var error: NSError? = nil
let store: NSPersistentStore?
do {
store = try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options)
} catch let error1 as NSError {
error = error1
store = nil
}
if store == nil {
print("Error adding persistent store: \(error)")
abort()
}
return privateContext
}
class func applicationDocumentsDirectory() -> NSURL {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[0]
}
}
I need to firstly confirm/clarify some points:
A) Is it legal/correct to create a full Core Data stack to use a context in a private queue the way I'm doing?
B) Would creating even a new NSManagedObjectModel from the same resource and same .sqlite file than the one used in the AppDelegate default Core Data stack cause problems?
About managing both contexts I have (the default in AppDelegate, let's call it mainContext, and the one I create in a private queue, let's call it privateContext):
The mainContext is intended to show the NSManagedObject information throughout the app.
The privateContext is intended to be used to call web services to get updated data, create the new NSManagedObject with the received information, and compare this new objects with the ones the app already have.
My questions regarding this are:
Should the privateContext be always used by calling performBlock or performBlockAndWait? Does that include all related operations, such s reading/inserting objects to the privateContext, and clearing/saving it?
The mainContext is supposed to be associated to the main queue/thread, right? So then all its related operations should be performed in main thread...
Having into account that privateContext has its own full Core Data stack... if I save its objects, would they be stored at the same .sqlite file than the ones when saving the mainContext? Or would such file be some way "duplicated"?
If privateContext should save data from its private queue, and mainContext should be used in main thread, would it cause any problem to fetch from the mainContext the data that was saved from the privateContext?
I need help to understand and correctly manage Core Data concurrency in my app, I'm making a mess with all this persistence staff and I'm occasionally finding errors in operations that seemed to work... thanks so much in advance.