SQLite data lost between Simulator App launches - ios

This feels strange and I somehow could not find an answer through search. This is an issue on Xcode iOS Simulator.
I am write an data persistence code. When instantiate the data store, I pretty much copied the following code fragment from Apple (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1).
Now, the issue seems - storeURL (when in debugging) is a URL containing a long numeric string and when relaunching the simulator, that long numeric string was changed. So the sqlite file is not reachable any more. I do believe I saved the data correctly because I debugged through the code (and context save api called with no error), I retrieved data after saving (without relaunching) and I see the data using command line sqlite tool against the sqlite file.
import UIKit
import CoreData
class DataController: NSObject {
var managedObjectContext: NSManagedObjectContext
init() {
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
self.managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
self.managedObjectContext.persistentStoreCoordinator = psc
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.URLByAppendingPathComponent("DataModel.sqlite")
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
}
}

This is what basically Matt said in the comment section. This issue is related to where to place the .sqlite file.

Related

Unable to destroy persistent store created with Core Data and SQLite

I have an iOS app where I want to start with a fresh Core Data database on every launch. The store type is SQLite.
However, when I call persistentStoreCoordinator.destroyPersistentStore(), I get an error 100% of the time.
Here is the code:
func destroyPersistentStore() {
guard let modelURL = Bundle.main.url(forResource: self.modelName, withExtension: "momd") else {
print("Missing data model - could not destroy")
return
}
do {
try persistentStoreCoordinator.destroyPersistentStore(at: modelURL, ofType: storeType, options: nil)
} catch {
print("Unable to destroy persistent store: \(error) - \(error.localizedDescription)")
}
}
The error is:
Unable to destroy persistent store: Error Domain=NSSQLiteErrorDomain
Code=14 "(null)" UserInfo={NSFilePath=.../AppName.app/ModelName.momd,
reason=Failed to truncate database} - The operation couldn’t be
completed. (NSSQLiteErrorDomain error 14.)
Even after this error, the app is able to save and access data in the store. The problem is that the initial data is being loaded on each launch, creating duplicates.
Here is the situation at the point where the call to destroyPersistentStore takes place:
The SQLite data file definitely exists and contains data
Happens on simulator or real device
The modelUrl is correct and points to the momd
Store type is SQLite
SQLite data file is saved in Documents directory
persistentStoreCoordinator.url(for: persistentStoreCoordinator.persistentStores.first!) is pointing to the file in the Documents directory.
I've searched online for answers and can't find anyone reporting this error, but I have the error in both this project and a simplified demo project. I cannot make destroyPersistentStore work at all.
Lastly, when I pause execution and po the persistentStoreCoordinator.managedObjectModel, the first line is:
po persistentStoreCoordinator.managedObjectModel
() isEditable 0, entities...
Could the isEditable issue be the problem? How would I change it?
You're conflating two objects in the Core Data stack:
The model is inside your app bundle, has the extension .momd, and contains information about your Core Data object definitions: what entities you have, what properties they have, their relationships, and so on.
The persistent store is a data file in your app's container (not in the bundle). You define its URL when you create or load persistent stores. It contains data for actual instances of model objects, rather than abstract definitions.
Rather than getting the URL of your model, I think you want to get the URL of a persistent store. You can do that by looking at the persistent store coordinator's persistentStores array, picking one, and getting its URL:
func destroyPersistentStore() {
guard let firstStoreURL = persistentStoreCoordinator.persistentStores.first?.url else {
print("Missing first store URL - could not destroy")
return
}
do {
try persistentStoreCoordinator.destroyPersistentStore(at: firstStoreURL, ofType: storeType, options: nil)
} catch {
print("Unable to destroy persistent store: \(error) - \(error.localizedDescription)")
}
}
This would destroy the first store; if you have multiple, you could instead loop over the persistent stores destroying them all, depending on your app's requirements.
iOS 15 version
// function to delete persistent store
func deletePersistentStore() {
let coordinator = self.persistentContainer.persistentStoreCoordinator
guard let store = coordinator.persistentStores.first else {
return
}
let storeURL = coordinator.url(for: store)
do {
if #available(iOS 15.0, *) {
let storeType: NSPersistentStore.StoreType = inMemoryStore ? .inMemory : .sqlite
try coordinator.destroyPersistentStore(at: storeURL, type: storeType)
} else {
let storeType: String = inMemoryStore ? NSInMemoryStoreType : NSSQLiteStoreType
try coordinator.destroyPersistentStore(at: storeURL, ofType: storeType)
}
}
catch {
print(error.localizedDescription)
}
}
Found this great solution:
import Foundation
import CoreData
extension NSManagedObjectContext
{
func deleteAllData()
{
guard let persistentStore = persistentStoreCoordinator?.persistentStores.last else {
return
}
guard let url = persistentStoreCoordinator?.url(for: persistentStore) else {
return
}
performAndWait { () -> Void in
self.reset()
do
{
try self.persistentStoreCoordinator?.remove(persistentStore)
try FileManager.default.removeItem(at: url)
try self.persistentStoreCoordinator?.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
}
catch { /*dealing with errors up to the usage*/ }
}
}
}

Is there any other way than `UserDefaults` to share data between `Today Widget Extension` and the main `Container Application` in iOS

I mean I want to know that is it possible to use CoreData which shares a common resource?
You have the options of sharing the SQLite and user default between your main app and the widgets.
If you want the SQLite to be shared between the Main App and the widget, please follow the steps.
create an app group. Click on the main project -> Capabilities -> App Group -> create a group.. example: group.com.appname.appgroup
While creating the persistent coordinator, create an SQLite file in the app group and provide the path.
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.appname.appgroup")!
let url = directory.appendingPathComponent("somename.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: [NSMigratePersistentStoresAutomaticallyOption:true,NSInferMappingModelAutomaticallyOption:true])
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() 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.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator}()
You can access the core data in widgets like the way you use in main app.
If you want to use the user defaults, then follow the below steps.
create a user deafult object.
var userDefaults =
UserDefaults(suiteName:"group.com.appname.appgroup")!
use this object for setting and retrieving the values.
You can share data between extensions and the host app via Keychain Sharing and App Groups (needs to be configured in the capabilities of your target).
Example for Keychain Sharing with KeychainAccess:
import KeychainAccess
let sharedKeychain = Keychain(service: "com.company.App.shared" , accessGroup: "TeamId.App")
sharedKeychain?["username"] = "Test"
Example for User Defaults:
var userDefaults = UserDefaults(suiteName: "group.com.company.App")!
Example for data in App Group:
let fileUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.company.App")!
To share data via CoreData just put the database file into the App Group storage.
Example with CoreStore:
import CoreStore
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "App")
let storagePathUrl = fileUrl.appendingPathComponent("App.sqlite")
do {
try dataStack.addStorageAndWait(SQLiteStore(fileURL: storagePathUrl, configuration: "Default", localStorageOptions: .
recreateStoreOnModelMismatch))
} catch let error {
print("Cannot set up database storage: \(error)")
}
return dataStack
}()

Data not persisted even after Core Data context save

I am facing some issues with the persistence of my core datas.
I am using a NSSQLiteStoreType persistentStore (see below).
In my application, everytime I am modifying, creating or deleting some data I am instantly saving these changes in my coredata by calling the function above saveContext()
BUT, time to time these changes are not persisted ! (this problem of non persistence is not always occuring, I still didnt succeed to reproduce this bug on purpose)
Here is the lifeCycle of one of these data:
Modification by the user
save context (patient.managedObjectContext where patient is the main entity linked to all others data entities)
synchronization with the backend using AlamoFire
set the remote id in the data after receiving the backend answer
save once again the context (still in Alamofire completion handler: executed in the main thread, checked by printing Thread.current)
Finally, when this bug happens, it does not affect only one data but seems to affect all data modified and saved from the current session (from the last opening until the closing)
Here is the code of my coredatacontroller:
class CoreDataController: NSObject {
static let shared = CoreDataController()
var managedObjectContext: NSManagedObjectContext
override init() {
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = Bundle.main.url(forResource: "xxx", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
let options = [ NSInferMappingModelAutomaticallyOption : true,
NSMigratePersistentStoresAutomaticallyOption : true]
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = psc
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.appendingPathComponent("xxx.sqlite")
do {
try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
} catch let error {
fatalError("Error migrating store: \(error)")
}
}
}
Here is where my coredatacontroller is instantiate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
let coreDataController : CoreDataController = CoreDataController.shared
// FIXME: determiner le comportement attendu pour le badge sur l'icone et l'appliquer !!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let window = window else {
fatalError("Pas de window !!!")
} ......
And here is my function used everytime i want to save a data in my coreData:
extension NSManagedObjectContext {
func saveContext() {
do {
try self.save()
} catch let error {
print("Failure to save context: \(error)")
}
}
}
Hope this problem will inspire you, and thank you by advance for your ideas !
EDIT 1:
After further investigation, all my process of synchronization (including alamofire requests) are done in the main thread (I checked on every step by printing Thread.current).
{number = 1, name = main}
thread of dataFromJson
{number = 1, name = main}
thread of conficthandler
{number = 1, name = main}
thread of fetchDataToSync
{number = 1, name = main}
Moreover, after checking differences between my backend database and my coredata, it seems that coredata fails to save every data until the crash of the app (voluntary or not) and then the relaunch of the app which will 'reboot' my coredata controller.
It's hard to be certain but there's one combination of details that spells trouble and might be the cause of this problem. You mention
save once again the context (still in the thread of Alamofire)
Looking through the code I see this:
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
And later on there's this:
do {
try self.save()
} catch let error {
print("Failure to save context: \(error)")
}
So, you have a context that uses main-queue concurrency, but you're saving changes off of the main thread by just calling save(). That's not good. Core Data is not thread-safe, and you're crossing threads.
To fix this, you really need to use the perform or performAndWait method on your managed context whenever you use it off of the main thread. That includes all access, everything that might touch the context in any way-- fetch requests, accessing properties or relations of managed objects, and saving changes. That's how Core Data wants to handle threading.

how to fix attempt to recursively call -save error with coredata?

I am getting this error randomly while saving in core data
Unresolved error Error Domain=NSCocoaErrorDomain Code=132001 "(null)" UserInfo={message=attempt to recursively call -save: on the context aborted, stack trace=(
Everything is working fine for last 3 month but recently I due to change in app I have to call a lot of fetch and save request and some of them are in loop and some in closure after making these changes I faced this error.
Here is code for coredata manager
import Foundation
import CoreData
class CoreDataStack {
private init() {
}
class func getContext () -> NSManagedObjectContext {
return CoreDataStack.managedObjectContext
}
// MARK: - Core Data stack
static var managedObjectContext: NSManagedObjectContext = {
var applicationDocumentsDirectory: URL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "com.cadiridris.coreDataTemplate" in the application's documents Application Support directory.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()
var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = Bundle.main.url(forResource: "Thyssenkrupp", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
let url = applicationDocumentsDirectory.appendingPathComponent("Thyssenkrupp.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
let options = [ NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption:true ]
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() 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.
print("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
//abort()
}
return coordinator
}()
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
class func saveContext () {
DispatchQueue.main.async {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() 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.
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
//abort()
}
}
}
}
}
Please provide any suggestion why this error coming
The problem was saving data to CoreData to frequently, Yes you can CoreData as Frequently as you want but it will through this error on console if you add/delete/update a data and save it in a loop doing this way will cause this error, not always but it's better to save CoreData after loop is complete. As Saving to Core Data is important in case where we perform Create,Update, Delete operation and we didn't save the CoreData app crashes/closed down for some reason then data will be lost from the point we last save the CoreData. Saving to CoreData is like a checkpoint everything is saved. So saving in a loop is not efficient way to do.

How should I correctly manage a full Core Data stack in private queue?

I handle two complete Core Data stacks in my app:
The one that is provided by default in AppDelegate.
A second stack I fully create in order to perform NSManagedObject updates in a private queue, to avoid blocking the UI.
I have a class to create the second "auxiliary" Core Data stack, and I do this way:
class CoreDataStack: NSObject {
class func getPrivateContext() -> NSManagedObjectContext {
let bundle = NSBundle.mainBundle()
let modelURL = bundle.URLForResource("MyApp", withExtension: "momd")
let model = NSManagedObjectModel(contentsOfURL: modelURL!)!
let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
let privateContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = psc
let documentsURL = CoreDataStack.applicationDocumentsDirectory()
let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")
let options = [NSMigratePersistentStoresAutomaticallyOption: true]
var error: NSError? = nil
let store: NSPersistentStore?
do {
store = try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options)
} catch let error1 as NSError {
error = error1
store = nil
}
if store == nil {
print("Error adding persistent store: \(error)")
abort()
}
return privateContext
}
class func applicationDocumentsDirectory() -> NSURL {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[0]
}
}
I need to firstly confirm/clarify some points:
A) Is it legal/correct to create a full Core Data stack to use a context in a private queue the way I'm doing?
B) Would creating even a new NSManagedObjectModel from the same resource and same .sqlite file than the one used in the AppDelegate default Core Data stack cause problems?
About managing both contexts I have (the default in AppDelegate, let's call it mainContext, and the one I create in a private queue, let's call it privateContext):
The mainContext is intended to show the NSManagedObject information throughout the app.
The privateContext is intended to be used to call web services to get updated data, create the new NSManagedObject with the received information, and compare this new objects with the ones the app already have.
My questions regarding this are:
Should the privateContext be always used by calling performBlock or performBlockAndWait? Does that include all related operations, such s reading/inserting objects to the privateContext, and clearing/saving it?
The mainContext is supposed to be associated to the main queue/thread, right? So then all its related operations should be performed in main thread...
Having into account that privateContext has its own full Core Data stack... if I save its objects, would they be stored at the same .sqlite file than the ones when saving the mainContext? Or would such file be some way "duplicated"?
If privateContext should save data from its private queue, and mainContext should be used in main thread, would it cause any problem to fetch from the mainContext the data that was saved from the privateContext?
I need help to understand and correctly manage Core Data concurrency in my app, I'm making a mess with all this persistence staff and I'm occasionally finding errors in operations that seemed to work... thanks so much in advance.

Resources