I want to update my iOS project to support CloudKit sync with Core Data.
What exactly is the URL I have to define in storeDirectory and what is the storeDirectory.appendingPathComponent?
struct PersistenceController {
static var shared = PersistenceController()
var persistentContainer: NSPersistentCloudKitContainer
init() {
persistentContainer = NSPersistentCloudKitContainer(name: "iOwe")
let storeDirectory = URL(fileURLWithPath: "/")
try? FileManager.default.createDirectory(at: storeDirectory, withIntermediateDirectories: true, attributes: nil)
let storeURL = storeDirectory.appendingPathComponent("iOwe.xcdatamodeld")
let description = NSPersistentStoreDescription(url: storeURL)
description.configuration = "Cloud"
description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "de.haufs.iowe")
persistentContainer.persistentStoreDescriptions = [description]
persistentContainer.loadPersistentStores { storeDescription, error in
guard error == nil else {
fatalError("Could not load persistent stores. \(error!)")
}
}
}
}
The app crashes with
Thread 1: Fatal error: Could not load persistent stores. Error Domain=NSCocoaErrorDomain Code=513 "Die Datei konnte nicht gesichert werden, da du nicht über die erforderlichen Zugriffsrechte verfügst." UserInfo={reason=No permissions to create file; code = 1}
Related
Currently, our app provides a backup functionality to user, where she can perform snapshot backup of app database.
We are using migratePersistentStore to achieve such functionality.
Here's our code snippet.
public static func cloneXXXDatabase(dstUrl: URL, directory: Directory, cloneTrash: Bool) -> Bool {
// Current already opened app database.
let coreDataStack = CoreDataStack.INSTANCE
guard let srcUrl = coreDataStack.persistentContainer.persistentStoreDescriptions.first?.url else { return false }
// New destination to backup current app database.
let coreDataNamedStack = CoreDataNamedStack(srcUrl)
// Have a new NSPersistentStoreCoordinator solely for migration purpose.
let psc = coreDataNamedStack.persistentContainer.persistentStoreCoordinator
// Open the SQLite. Is it fine for 2 different NSPersistentStoreCoordinator to access 1 same SQLite?
guard let srcStore = psc.persistentStore(for: srcUrl) else { return false }
do {
// Reference: https://www.avanderlee.com/swift/write-ahead-logging-wal/
// This is to ensure only 1 SQLite file is produced, without WAL & SHM.
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
try psc.migratePersistentStore(srcStore, to: dstUrl, options: options, withType: NSSQLiteStoreType)
} catch {
error_log(error)
return false
}
return true
}
In order to avoid there are SHM and WAL files generated at backup destination folder, we are trying to use
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
as migration option. However, it doesn't seem to work because I can observe the following 3 files generated at backup destination folder.
xxx.sqlite
xxx.sqlite-shm
xxx.sqlite-wal
I was wondering, is there a way to only have single xxx.sqlite generated, when performing migratePersistentStore?
Thank you.
Here's the code of CoreDataStack (Core data stack for main app), and CoreDataNamedStack (Core data stack which points to the backup destination)
CoreDataStack (Core data stack for main app)
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
}
CoreDataNamedStack (Core data stack which points to the backup destination)
class CoreDataNamedStack: CoreDataStackable {
let url: URL
init(_ url: URL) {
self.url = url
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "xxx", managedObjectModel: NSManagedObjectModel.wenote)
let storeDescription = NSPersistentStoreDescription(url: url)
container.persistentStoreDescriptions = [storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
}
I'm trying to move my core data location so I can share it between the iPhone app and an Apple Watch app. Investigation so far shows that I need to use app containers for both apps to have access to the same core data information.
I've set the group up, and its added to both targets
group.Desbrina.dpl.Diamond-Painting-Logbook.shared
So, I've been following a tutorial at the below site,
https://medium.com/#manibatra23/sharing-data-using-core-data-ios-app-and-extension-fb0a176eaee9
and modified my app delegate, but the existing data isn't being loaded and new data isn't being saved. From what I can find I need to migrate the existing core data to the new location. I've not found anything yet that explains how to do it with the latest version of swift.
Currently the working version is loaded using
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Diamond_Painting_Logbook")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
The modified code for the new location I have as.
/*lazy var persistentContainer: NSPersistentContainer = {
let container = NSCustomPersistentContainer(name: "Diamond_Painting_Logbook")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()*/
class NSCustomPersistentContainer: NSPersistentContainer {
override open class func defaultDirectoryURL() -> URL {
var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.Desbrina.dpl.Diamond-Painting-Logbook.shared")
storeURL = storeURL?.appendingPathComponent("Diamond_Painting_Logbook.sqlite")
return storeURL!
}
}
I've tried using the below to move the store, but it errors on the mirgratePersisantStore line
persistentContainer = NSPersistentContainer(name: "Diamond_Painting_Logbook")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Could not create CoreData store: \(error)")
}
var nURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.Desbrina.dpl.Diamond-Painting-Logbook.shared")
nURL = nURL?.appendingPathComponent("Diamond_Painting_Logbook.sqlite")
if let newURL = nURL {
do {
let psc = self.persistentContainer.persistentStoreCoordinator
let store = psc.persistentStores[0]
NSLog("7 *****************")
try psc.migratePersistentStore(store, to: newURL, options: nil, withType: NSSQLiteStoreType)
NSLog("8 *****************")
} catch {
}
}
}
The error is
2019-11-23 19:55:45.132081+0000 Diamond Painting Logbook[6707:1813609] 7 *****************
2019-11-23 19:55:45.132863+0000 Diamond Painting Logbook[6707:1813609] [logging-persist] cannot open file at line 43353 of [378230ae7f]
2019-11-23 19:55:45.132901+0000 Diamond Painting Logbook[6707:1813609] [logging-persist] os_unix.c:43353: (0) open(/private/var/mobile/Containers/Shared/AppGroup/BF375BFE-4AF0-4F78-BDEE-C4A4A12493B3/Diamond_Painting_Logbook.sqlite) - Undefined error: 0
2019-11-23 19:55:45.132917+0000 Diamond Painting Logbook[6707:1813609] [logging] API call with unopened database connection pointer
2019-11-23 19:55:45.132929+0000 Diamond Painting Logbook[6707:1813609] [logging] misuse at line 162611 of [378230ae7f]
2019-11-23 19:55:45.133370+0000 Diamond Painting Logbook[6707:1813609] [error] error: -addPersistentStoreWithType:SQLite configuration:PF_DEFAULT_CONFIGURATION_NAME URL:file:///private/var/mobile/Containers/Shared/AppGroup/BF375BFE-4AF0-4F78-BDEE-C4A4A12493B3/Diamond_Painting_Logbook.sqlite/ options:{
NSPersistentStoreRemoveUbiquitousMetadataOption = 1;
} ... returned error NSCocoaErrorDomain(256) with userInfo dictionary {
NSFilePath = "/private/var/mobile/Containers/Shared/AppGroup/BF375BFE-4AF0-4F78-BDEE-C4A4A12493B3/Diamond_Painting_Logbook.sqlite";
NSSQLiteErrorDomain = 14;
}
CoreData: error: -addPersistentStoreWithType:SQLite configuration:PF_DEFAULT_CONFIGURATION_NAME URL:file:///private/var/mobile/Containers/Shared/AppGroup/BF375BFE-4AF0-4F78-BDEE-C4A4A12493B3/Diamond_Painting_Logbook.sqlite/ options:{
NSPersistentStoreRemoveUbiquitousMetadataOption = 1;
} ... returned error NSCocoaErrorDomain(256) with userInfo dictionary {
NSFilePath = "/private/var/mobile/Containers/Shared/AppGroup/BF375BFE-4AF0-4F78-BDEE-C4A4A12493B3/Diamond_Painting_Logbook.sqlite";
NSSQLiteErrorDomain = 14;
}
I am in this scenario:
My app extension (share extension) insert a new entry using CoreData and the CoreData's SQLite file stored in the app group's shared directory.
While switching back to the main app, the main context does not update.
I used CoreDataStack.shared.mainContext.reset() but it does not work.
My core stack core (both of my main app and the app extension use this code):
import CoreData
public class CoreDataStack {
let appID = "xxx"
let modelFileName = "xxx"
let defaultAppGroupID = "xxx"
public static let shared = CoreDataManager()
public var mainContext: NSManagedObjectContext {
return mainPersistentContainer.viewContext
}
// MARK: - Core Data stack
lazy var managedObjectModel: NSManagedObjectModel = {
let bundle = Bundle(for: type(of: self))
let momdURL = bundle.url(forResource: modelFileName, withExtension: "momd")!
return NSManagedObjectModel(contentsOf: momdURL)!
}()
lazy var mainPersistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelFileName, managedObjectModel: managedObjectModel)
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: defaultAppGroupID)!.appendingPathComponent("\(modelFileName).sqlite"))]
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
public func saveMainContext() {
let context = mainPersistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
fileprivate func applicationDocumentsDirectory() -> URL {
let fileManager = FileManager.default
if let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.io.wildcat.InfoFlow") {
return url
} else {
let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
return urls[0]
}
}
}
I submitted a Code-Level support and here is the answer.
There are two workarounds you can consider:
Reload the data from the store when your main app is back to the foreground. After that, reload the data.
context.reset()
// MARK: Reload the data
// NSFetchedResultsController.performFetch()
// UITableView.reloadData()
Persistent History Tracking (also enhanced in WWDC 19). Reference: https://stoeffn.de/posts/persistent-history-tracking-in-core-data/
In new version I start using Core Data. But when I try to migrate from an old database to the new one, I'm getting an error:
let dbName = "db.sqlite:"
guard let modelURL = Bundle.main.url(forResource: "Ex", withExtension: "momd"),
let model = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("model not found")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
let databaseUrl = URL.defaultUrl.appendingPathComponent(dbName)
do {
try psc.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: databaseUrl,
options: [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true])
} catch {
fatalError("database migration error")
}
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
context.persistentStoreCoordinator = psc
Error message:
Error Domain=NSCocoaErrorDomain Code=256 "The file couldn’t be opened."
UserInfo={NSUnderlyingException=I/O error for database at
/var/mobile/Containers/Data/Application/dsdasda-6Basdsd9C-4AE9-sad-sd/Documents/db.sqlite.
SQLite error code:1, 'no such table: Z_METADATA', NSSQLiteErrorDomain=1}
error: The file couldn’t be opened.
How i can resolve that?
The trailing colon in the dbName looks extraneous. ("db.sqlite**:**")
I have checked some websites from apple, most appear to be outdated and lacking because I needed convert the code after pasting them into XCode8. I have an old code but I can't figure out how to migrate them to the new style.
Here is my old code:
self.psc = {
let psc = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
do {
_ = try psc.addPersistentStore(
ofType: NSInMemoryStoreType, configurationName: nil,
at: nil, options: nil)
} catch {
fatalError()
}
return psc
Here is what I have done so far,
lazy var testPersistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Test Data Store")
do {
try container.persistentStoreCoordinator.addPersistentStore(ofType:
NSInMemoryStoreType, configurationName: "Test Persistent Store",
at: nil, options: [:])
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(error), \(nserror.userInfo)")
}
There is exception in the try statement.
Error Domain=Foundation._GenericObjCError Code=0 "(null)" fatal error:
Unresolved error nilError
You can continue using the old approach. It's not deprecated, and NSPersistentContainer isn't required.
If you want the newer approach, use the new NSPersistentStoreDescription class, which handles all the stuff that could be specified when adding a persistent store.
You can try below code for newer approach :
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
// Need to add NSPersistentStoreDescription ===
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { [weak self](storeDescription, error) in
if let error = error {
NSLog("CoreData error \(error), \(error._userInfo)")
self?.errorHandler(error)
}
})
return container
}()
Hope this help you.