This NSPersistentStoreCoordinator has no persistent stores (device locked). It cannot perform a save operation. specialized static method

拈花ヽ惹草 提交于 2020-12-06 12:28:45

问题


App crashes randomly on production when trying to save or update a record.

It's a VOIP app, getting background CallKit pushes and on some conditions, writes them so CoreDate DB. I suspect that that's what's crashing the app but I could not find any reference to it online.

Tried reproducing this issue locally with no luck, could be because it's impossible to debug with Xcode before you unlock your phone for the first time.

This is my CoreDate code from AppDelegate:

    lazy var persistentContainer: NSPersistentContainer = {

        let container = NSPersistentContainer(name: "Model")

        var persistentStoreDescriptions: NSPersistentStoreDescription

        let storeUrl =  FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.appname")!.appendingPathComponent("Model.sqlite")


        let description = NSPersistentStoreDescription()
        description.shouldInferMappingModelAutomatically = true
        description.shouldMigrateStoreAutomatically = true
        description.url = storeUrl

        container.persistentStoreDescriptions = [NSPersistentStoreDescription(url:  FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.appname")!.appendingPathComponent("Model.sqlite"))]

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                Crashlytics.sharedInstance().recordError(error)
                #if DEBUG
                fatalError("Unresolved error \(error), \(error.userInfo)")
                #endif

            }
        })
        return container
    }()

    // MARK: - Core Data Saving support
    func saveContext () {
        let context = persistentContainer.viewContext
        managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() 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.
                Crashlytics.sharedInstance().recordError(error)
            }
        }
    }

The function where the crash happens:

Call.swift :

let callEntity = NSEntityDescription.entity(forEntityName: "Call", in: managedContext)!

  static func upsertCall(call: Call?) {
        if(call == nil){
            return
        }

        //validation here..
        //..

        do {
            managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
            try managedContext.save()
        } catch {
            let nserror = error as NSError
            NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
            Crashlytics.sharedInstance().recordError(error)
        }
    }

Running - Swift 5 - Xcode 10.2


回答1:


It's a VOIP app, getting background CallKit pushes and on some conditions, writes them so CoreDate DB. I suspect that that's what's crashing the app but I could not find any reference to it online.

Tried reproducing this issue locally with no luck, could be because it's impossible to debug with Xcode before you unlock your phone for the first time.

Some time ago I've run into similar problem caused by app being ran when there are no access rights to the DB file.

I've used following setup to debug this problem:

  1. Enable (implement) logging to file (for instance with CocoaLumberjack, but any logging will do). Make sure that file access is set to NSFileProtectionNone so it can be written to at all times!
  2. Log everything you can around problematic code and all app delegate callbacks related to application lifetime.
  3. Install app on a device. It must have passcode and lock enabled!

Now using above setup, there were multiple scenarios used to trigger this problem:

  1. Restart phone, do not enter passcode, let it lie down, older iOS used to spawn the app just to perform background fetch (if set up) after few hours/days.
  2. Restart phone, do not enter passcode, trigger remote push notification or voip call.

In both scenarios after analyzing logs, it became clear that app was woken up in the background but file protection has not been lifted (as no passcode has been entered).

Now, to be honest I don't know if above issues will still reproduce, but your issue seems to be really similar.

So - what about solution?

This problem affects both file and keychain access. I've seen numerous apps not implementing that properly (result: user being randomly logged out as app could not access keychain to query auth token) and solution that is often shared, is to lift all access to file/keychain by specifying kSecAttrAccessibleAlways for keychain entries or mentioned above NSFileProtectionNone for files.

I believe that while that works, this is generally poor idea from security point of view.

What can be done, is to defer startup of app services and all setup until it is possible to access files. If app is supposed to write to DB while being in the background, set file permissions to at least NSFileProtectionCompleteUntilFirstUserAuthentication and open DB only when write access is possible. Checking isProtectedDataAvailable is not a great solution - from my experience if always returns false if screen is locked and passcode is set, and that does not take into account individual file access permissions.




回答2:


I saw similar error in my crashes: CoreData: -[NSPersistentStoreCoordinator _coordinator_you_never_successfully_opened_the_database_device_loc... + 52

My understanding is that app was doing something, but access to core data was not allowed. In my case it was when saving data to core data from notification.

I added this check to methods which save to core data:

if !UIApplication.shared.isProtectedDataAvailable {
            return
        }


来源:https://stackoverflow.com/questions/56849500/this-nspersistentstorecoordinator-has-no-persistent-stores-device-locked-it-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!