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
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:
NSFileProtectionNone so it can be written to at all times!Now using above setup, there were multiple scenarios used to trigger this problem:
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.
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