iosswiftcore-data

Core Data sometimes loses data


We are running an App through Citrix Secure Hub, it seems that sometimes there is a rollback with loosing some Data in CoreData.

As i understand, CoreData is having something like an working copy of all the objects, and sometimes its tries to persist that on the filesystem.

Well tried to simulate the behavior but without any success, we could not find out any data loss or rollbacked data in our test environment.

So is there a way to force iOS to write the current "working copy" on the disk to prevent any data loss when using too much memory (and maybe crash)? We call our save function after

As we already found out:

We were NOT using:

func applicationWillResignActive(_ application: UIApplication) {
      print("applicationWillResignActive")
}

to save the context, could this be a problem (we are already saving the context after every created object) ?

At the Moment we dont really handle problems when the context could not be saved, are there any recommendations how to handle that in a productive environment? And is it a good thing to maybe crash to app to prevent the user from struggeling with data loss?

Edit: this is the used Core Data Handler:

import Foundation
import CoreData

let context = CoreDataManager.shared.managedObjectContext

func saveContext(_ completion: (() -> Void)? = nil) {

     CoreDataManager.shared.save(completion)
}

func saveContextSync() {

     CoreDataManager.shared.saveSync()
}

class CoreDataManager: NSObject {

    static let shared = CoreDataManager()

    lazy var managedObjectContext: NSManagedObjectContext = {

    var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

    managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

    return managedObjectContext
}()

And our save functionality:

@objc func save(_ completion: (() -> Void)?) {

    saveAsync(completion)
}

func saveAsync(_ completion: (() -> Void)?) {

    func save() {

        context.perform {
            do { try context.save() }
            catch {
              // HERE WE NEED TO HANDLE IT FOR A PRODUCTIVE ENVIRONMENT
            }

            completion?()
        }
    }

    if Thread.isMainThread {
        save()
    } else {
        DispatchQueue.main.async {
            save()
        }
    }

}

func saveSync() {

    func save() {
        context.performAndWait {
            do { try context.save() }
            catch { print(error)
                // TRY TO REPRODUCE MEMORY LOSS APP TO SEE WHAT HAPPENS
                abort()
            }
        }
    }

    if Thread.isMainThread {
        save()
    } else {
        DispatchQueue.main.sync {
            save()
        }
    }
}

Edit 2: This question in Objective C should be very similar:

Core Data reverts to previous state without apparent reason

Edit 3: It seems that there is no crash, some users telling me that they are adding data, then just press the home button and after a couple of hours the data from the last "task" is lost.


Solution

  • There are three possible causes.

    Write Conflicts

    Core data generally wants writes to be done in a single synchronous way. If you write in multiple ways at the same time to the same object (even if they are touching different properties and don't strictly conflict), it will be a merge conflict. You can set a merge policy (by default the value is 'error' - meaning don't apply the changes) but that is really a bad solution because you are telling core-data to lose information silently. see NSPersistentContainer concurrency for saving to core data for a setup to prevent merge conflicts.

    Closing the app with unsaved data

    If you setup your core-data correctly this shouldn't happen. The correct way to setup core data to only read from the 'viewContext' and write in a single synchronous way. Each writing is done in a single atomic block and the UI is only updated after it is saved. If you are displaying information from a context that is not saved to disk this can be a problem. For example it appears that your app only uses a single main-thread context for both reading and writing. Making changes to that context and not calling save will leave the app in a state where there are major changes that are only in memory and not on disk.

    There is an error saving to disk

    This is by far the rarest event, but there are users that have really really full disks. If this happens there is NOTHING that you can do to save. There is physically no room left on the disk. Generally the correct thing to do is to tell the user and leave it at that.


    Without knowing more about your particular setup it hard to say for certain what your problem is. I would recommend the following setup:

    1. use NSPersistentContainer
    2. only read from the viewContext and never write to it.
    3. make an operation queue for writing to core data
    4. for every operation, create a context, make changes to it, and then save. Do not pass any managed object into or out of these blocks.

    This should deal with all but the third problem.