Setting persistentStoreDescriptions causes no CoreData objects to be saved - ios

I am trying to ensure that all of our CoreData is protected using Data Protection. When I try to set a NSPersistentStoreDescription on my container, no CoreData objects are saved. If I comment out the line indicated below, all objects are saved (and read) just fine. If I enable the line, nothing gets saved (or perhaps the read silently fails?). There are no errors generated and there are no logs generated. I do have the Data Protection entitlement in my provisioning profile (matching completeUnlessOpen). I've gotta be missing something very basic.
This is Xcode 8.2.1 (8C1002)
Can anyone offer any insight / advice?
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "my_app")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
description.setOption(FileProtectionType.completeUnlessOpen as NSObject?, forKey: NSPersistentStoreFileProtectionKey)
// *** ALLOWING THIS NEXT LINE TO EXECUTE CAUSES PROBLEM ***
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()

I understand that this question has been asked long time back, but I'm still going to post an answer so that anyone stuck at this problem gets some help.
In the above code snippet you posted in the question, you did not initialize NSPersistentStoreDescription with SQLITE file URL.
So, it does not know what persistent container it represents. Please refer below working code.
let container = NSPersistentContainer(name: "AppName")
if let storeDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
let sqliteURL = storeDirectory.appendingPathComponent("AppName.sqlite")
//Set Protection for Core data sql file
let description = NSPersistentStoreDescription(url: sqliteURL)
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
description.setOption(FileProtectionType.complete as NSObject, forKey: NSPersistentStoreFileProtectionKey)
container.persistentStoreDescriptions = [description]
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in...}
Let me know if this works for you and please accept the answer if it does, so that others will know.

Your error is actually because you're setting options that should relate to Core Data. File protection is done under FileManager setAttributes function calls.
However, the answer to the underlying question is that you don't need to add file protection to individual files - they're set to the default protection you declare in your entitlement.
This can be verified by printing the output of:
let attributes = try FileManager.default.attributesOfItem(atPath: url.relativePath)

Related

An error occurred during persistent store migration (Cannot migrate store in-place) Core Data

I added new model version and select it like default one. Then I added new attribute to existing entity in new model version. To test migration I installed previous version of app and add filled with all data in app. Then I install new changes and it work on simulator. If I move that build to TestFlight and test via real device then I get this error:
Fatal error: ###persistentContainer: Failed to load persistent stores:Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={sourceURL=file:///private/var/mobile/Containers/Shared/AppGroup/DBB80C75-1B07-4318-8BA3-3F4FFC14FBD7/AppName.sqlite, reason=Cannot migrate store in-place: near "null": syntax error, destinationURL=file:///private/var/mobile/Containers/Shared/AppGroup/DBB80C75-1B07-4318-8BA3-3F4FFC14FBD7/AppName.sqlite, NSUnderlyingError=0x281958180 {Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={reason=near "null": syntax error, NSSQLiteErrorDomain=1, NSUnderlyingException=near "null": syntax error}}}
I need to use CoreData for Widget's target as well. Code is here :
final class CoreDataStack {
static let shared = CoreDataStack()
var context: NSManagedObjectContext { persistentContainer.viewContext }
var container: NSPersistentContainer { persistentContainer }
private let containerName = "AppName"
private var persistentContainer: NSPersistentContainer!
// MARK: - Setup
func setup() {
guard let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app_group")?.appendingPathComponent(containerName + ".sqlite") else {
fatalError("Error finding Model from File Manager")
}
let container = NSPersistentContainer(name: containerName)
let description = NSPersistentStoreDescription(url: storeURL)
description.type = NSSQLiteStoreType
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true
container.persistentStoreDescriptions = [description]
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = appTransactionAuthorName
container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
let crashlitycService: CrashlitycUseCase = CrashlitycService()
crashlitycService.record(error: .errorForAnalytic(error), file: #file, function: #function, line: #line)
fatalError("###\(#function): Failed to load persistent stores:\(error)")
}
})
self.persistentContainer = container
}
}
iOS 16.2 Xcode 14.1
Similar issues I found here, but without any success:
An error occurring during Core Data persistent store migration in iOS 13
iOS app crashing on launch screen due to core data migration
Please help me to figure out how can I do this migration?

Custom entity migration in swift coredata not working

I changed the type of one of the fields in an entity i created in iOS from a dictionary to a list and created a new version for that entity which is set as default but kept the same name, but migration is not working as whenever i try to iterate that field in a deserialized object it appears to still be a dictionary due to unrecognized selector error for indexAt i receive.
I created a subclass of NSEntityMigrationPolicy to migrate a field from a dictionary to string type ([String : TimerData] -> [TimerData]) with a method like so:
class SessionTransformationPolicy : NSEntityMigrationPolicy {
#objc func transformTimerRows(data: [String : TimerData]?) -> [TimerData] {
return Array(data!.values).map { orig in
TimerData(old: orig)
}
}
}
I created a mapping model and set it to this migration policy, but whenever i put breakpoints or print statements in the migration policy it doesnt get executed, i made sure to specify in the model mapping the method i created based on online sources:
FUNCTION($entityPolicy, "transformTimerRowsWithData:" , $source.timerRows)
I've done a bunch of research into this issue and the only solution i've found was to go into the app delegate and change shouldInferMappingModelAutomatically for the container to false but this just results in no data being loaded and the migration policy still not being called, can someone tell me what i'm doing wrong?
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
let description = NSPersistentStoreDescription()
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = false
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
else {
}
})
return container
}()

Swift 2 to Swift 3 Core Data in Xcode 9 - How can I port over existing Core Data "data" already saved?

I have an older project/app that I want to bring up to a 3.x version of Swift from Swift 2. In the course of porting the code over I recreated a new project with the same bundle id to start fresh and imported the code over.
Everything is working now in Swift 3.2 in Xcode 9 except for Core Data. Any device running an older version of the app crashes when accessing Core Data.
THe specific error is in the loading of the NSManagedObjectModel.
OLD SWIFT 2 WAY:
My CoreData File is called: "MyApp.xcdatamodeld"
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle().URLForResource("MyApp", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
NEW SWIFT 3 WAY:
My new CoreData File is called: "MyApp.xcdatamodel"
lazy var managedObjectModel: NSManagedObjectModel = {
if let modelURL = Bundle.main.url(forResource: "MyApp", withExtension: "xcdatamodel") {
return NSManagedObjectModel(contentsOf: modelURL)!
} else {
let modelURLOld = Bundle.main.url(forResource: "MyApp", withExtension: "momd")
return NSManagedObjectModel(contentsOf: modelURLOld!)!
}
}
When I run the code I get the following errors:
CoreData: annotation: Failed to load optimized model at path '/var/containers/Bundle/Application/50367482-FC2C-4553-B04B-68AD922B8128/MyApp.app/MyApp.momd/MyApp.omo'
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSFetchRequest could not locate an NSEntityDescription for entity name 'UMyAppModel''
So my new app code is not finding the the files to load for CoreData.
How can I can load these older CoreData files into my app? If need be I can set up a migration but the issue is that I can't see the older Core data files.
One way I can get the app to run is to add the following code:
let objectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])
return objectModel!
However none of the previous historical data is loaded into the app.
How can I migrate or get access to the original Coredata data to migrate into the new Coredata files on devices that have the old code version (swift 2) of the app?
Fixed the issue. It was related to the way the NSPersistentContainer was being initialized in my app. The swift 3 boiler plate code was not migrating to the new data store nor looking for the old sqlite db (which I suspect was ultimately the main cause of the data not being found).
My code:
lazy var persistentContainer: NSPersistentContainer = {
let modelURL = Bundle.main.url(forResource: "MyApp", withExtension: "momd")!
let container = NSPersistentContainer(name: "MyApp", managedObjectModel: NSManagedObjectModel(contentsOf: modelURL)!)
let documentsDirectory = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
let storeUrl = self.applicationDocumentsDirectory.appendingPathComponent("MyApp.sqlite")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeUrl)]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()

How to solve Core Data error: Attempt to add read-only file at path when trying to access Core Data container in the app extension today widget?

I am trying to access a database using Core Data inside a today extension widget.
The function that creates the Core Data container is this:
func createContainer(completion: #escaping (NSPersistentContainer) -> ()) {
let applicationGroupIdentifier = "group.com.name.exampleApp.sharedContainer"
guard let newURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier)
else { fatalError("Could not create persistent store URL") }
let persistentStoreDescription = NSPersistentStoreDescription()
persistentStoreDescription.url = newURL
let container = NSPersistentContainer(name: "ContainerName")
container.persistentStoreDescriptions = [persistentStoreDescription]
container.loadPersistentStores { (_, error) in
guard error == nil else { fatalError("Failed to load store:\(String(describing: error))") }
DispatchQueue.main.async { completion(container) }
}
}
However, I get the following error:
CoreData: error: Attempt to add read-only file at path
file:///private/var/mobile/Containers/Shared/AppGroup/C9324B65B-265C-4264-95DE-B5AC5C9DAFD0/
read/write. Adding it read-only instead. This will be a hard error in
the future; you must specify the NSReadOnlyPersistentStoreOption.
If I specify the NSReadOnlyPersistentStoreOption like this:
persistentStoreDescription.isReadOnly = false
I get the error:
Failed to load store:Optional(Error Domain=NSCocoaErrorDomain Code=513
"The file couldn’t be saved because you don’t have permission."):
What might be the cause and the solution?
Had the same problem and solved it by adding
let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.identifier")!
let url = directory.appendingPathComponent("MyDatabaseName.sqlite")
CoreData probably tried to open the base folder as its sqlite database, which did not go so well.
Adding isReadOnly didn't allow CoreData to repurpose the Folder either :)
Source: https://github.com/maximbilan/iOS-Shared-CoreData-Storage-for-App-Groups
In extension's info.plist set: NSExtension --> NSExtensionAttributes --> RequestOpenAccess --> YES
In Settings on device set: Your app --> (Extension type - eg.: Keyboard) --> Allow Full Access

SQLite data lost between Simulator App launches

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.

Resources