I am using Core Data for the first time in a project made in XCode 8, swift 3. I have used background context (calling container.performBackgroundTask block..) to save the data and main context to fetch the data. When my app re-launches, the data I saved in the private background context is deleted.
Please tell me where I went wrong !!!
Here I call CoreDataManager class save context method in applicationDidEnterBackground and applicationWillTerminate methods of AppDelegate class:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
lazy var coreDataMgrInstance = CoreDataManager.sharedInstance
func applicationDidEnterBackground(_ application: UIApplication) {
coreDataMgrInstance.saveContext()
}
func applicationWillTerminate(_ application: UIApplication) {
coreDataMgrInstance.saveContext()
}}
Here is my Singleton class CoreDataManager to initiate NSpersistentContainer
class CoreDataManager: NSObject {
class var sharedInstance: CoreDataManager {
struct Singleton {
static let instance = CoreDataManager()
}
return Singleton.instance
}
private override init() {
super.init()
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "E_CareV2")
let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
func saveContext(){
print("saveContext")
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Failure to save main context \(nserror), \(nserror.userInfo)")
}
}}
Now this is the class where I save and fetch the data from Core Data
class SenderViewController: UIViewController {
var persistentContainer: NSPersistentContainer!
override func viewDidLoad() {
super.viewDidLoad()
persistentContainer = CoreDataManager.sharedInstance.persistentContainer
let results = self.fetchPersistentData(entityName: "School", withPredicate: nil)
print("results \n \(results)")
}
@IBAction func enterPressed(_ sender: Any) {
self.persistentContainer.performBackgroundTask({ (backgroundContext) in
backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
let schoolentity = NSEntityDescription.insertNewObject(forEntityName: "School", into: backgroundContext) as! School
schoolentity.schoolName = "ABC"
schoolentity.schoolWebSite = "http://anywebsite.com/"
do {
try backgroundContext.save()
} catch {
fatalError("Failure to save background context: \(error)")
}
})
}
func fetchPersistentData(entityName: String?, withPredicate: NSPredicate?) -> [NSManagedObject]{
let context = self.persistentContainer.viewContext
let request: NSFetchRequest<School> = School.fetchRequest()
let newentity = NSEntityDescription.entity(forEntityName: entityName!, in: context)
request.entity = newentity
request.returnsObjectsAsFaults = false
do {
let searchResults = try context.fetch(request) as [NSManagedObject]
return searchResults
} catch {
print("Error with request: \(error)")
}
return []
}
Actually the lightweight migrations are enabled by default as you can see on the screenshot
So you can safely delete these lines:
let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
After that everything should work.
There are two ways to use NSPersistentContainer
- the simple way and the correct way. You are mixing them which is leading to problems. Apple is a little inconsistent on this in its documentation so it is understandable that you are confused.
The Simple Way - The simple way is only use the viewContext
both for reading and for writing and only from the main thread. There are never any conflicts because core-data is assessed via a single thread. The problem with this approach is that you can't run anything in a background thread. So if you are importing a lot of data the UI will freeze. If you have a super simple app (a small task list?) this will work OK, but not something that I would ever recommend for a serious app. It is OK for a testing app for a beginner to learn core-data.
The Correct Way - the correct way is to NEVER write to the viewContext
EVER. Apple documents this in NSPersistentContainer
documentation (but also in its template creates a save method for the viewContext?!). In this setup all writes to core data MUST go through performBackgroundTask
and you have to call save
on that context before the end of the block. You also need an operation queue to make sure that there are no merge conflicts see NSPersistentContainer concurrency for saving to core data. This setup is a lot harder to do correctly. Objects from performBackgroundTask
contexts cannot leave the block and objects from the viewContext
cannot be used in the block. The complier doesn't help you with this so it is something that you always need to watch out for.
Your problem is mergePolicy
. mergePolicy
is evil. If you have conflicts in core-data you have already done something wrong and any merge policy will lose data. In the simple setup there are no conflicts because it is all on one thread. In the correct setup there is no conflicts because of the queue you created when using performBackgroundTask
. The problem is that if you use BOTH performBackgroundTask
and write the the viewContext
you can get conflicts and you will lose data. Personally, I think it is better to have no mergePolicy
and crash then to silently lose data.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With