Search code examples
iosswiftcore-data

Why my core data setup didn't finish to setup on time?


The following issue only sometimes appears, and never on my device. It happens a lot of times to users of my app.

enter image description here

I tried to regenerate that issue on my own device, and I simply commented it out where I setup my core data stack. And the error is the following:

enter image description here

In my opinion it is the same, and the reason why it happens on production is that... core data stack didn't finish to setup before it is used in the app. Am I right?

Look at below code. This is how I setup my Core Data:

class CoreDataManager {
    static var shared = CoreDataManager()
    private var coordinator: NSPersistentStoreCoordinator?
    var rootContext: NSManagedObjectContext?
    var defaultContext: NSManagedObjectContext?
    func setup() {
    
        guard coordinator == nil && defaultContext == nil else {
            return
        }
        if let managedObjectModel = NSManagedObjectModel.defaultModel {
            coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
        
            var storePath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: SharedGroupName)
        
            storePath = storePath!.appendingPathComponent("FieldService.sqlite")
        
            let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
        
            do {
                try coordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storePath, options: options)
                rootContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
                rootContext?.persistentStoreCoordinator = coordinator
                rootContext?.obtainPermanentIdsBeforeSaving()
                rootContext?.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
                defaultContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
                defaultContext?.setupDefaultContext()
                defaultContext?.obtainPermanentIdsBeforeSaving()
                defaultContext?.parent = rootContext
            } catch let error as NSError {
                print("SUPER ERROR>>>>>>>>>")
                print(error)
            }
        }
    }
}

And this is simply called here:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    CoreDataManager.shared.setup()
    // another stuff
}

Issue for answer from Vadian:

enter image description here


Solution

  • As CoreDataManager is a singleton it makes no sense to implement an extra setup method – which will be called only once anyway – and perform nil checks. A better way is to implement init and do all the setup stuff there.

    All properties in a Core Data stack – especially in a singleton – are supposed to be non-optional. Properties which depend on each other can be initialised lazily.

    And NSPersistentStoreCoordinator is outdated. Apple introduced NSPersistentContainer many years ago.

    A contemporary implementation of a Core Data stack is something like this, I commented out some lines which threw compile errors

    class CoreDataManager {
        static let shared = CoreDataManager()
        
        private let container: NSPersistentContainer
        
        init() {
            container = NSPersistentContainer(name: "FieldService")
            let storedescriptionOptions = NSPersistentStoreDescription()
            storedescriptionOptions.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
            storedescriptionOptions.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
            container.persistentStoreDescriptions = [
                NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: SharedGroupName)!.appendingPathComponent("FieldService.sqlite")),
                storedescriptionOptions
            ]
            container.loadPersistentStores { _, error in
                if let error { fatalError(error.localizedDescription) }
            }
        }
      
        lazy var rootContext : NSManagedObjectContext = {
            let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
            context.persistentStoreCoordinator = container.persistentStoreCoordinator
            // context.obtainPermanentIdsBeforeSaving()
            context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
            return context
        }()
        
        lazy var defaultContext : NSManagedObjectContext = {
            let context = self.container.viewContext
            // context.setupDefaultContext()
            // context.obtainPermanentIdsBeforeSaving()
            context.parent = rootContext
            return context
        }()
    }