UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Advice on live switching of CoreData persistence controllers

Forums > SwiftUI

I have decided to make a change in my app. My app is arranged around “projects”. Each project is a se tof work someone does. Previously, I kept all projects in a single CoreData instance. That works fine but has some disadvantages. Mainly I want the user to be able to open and close projects much like they would with a word processor.

I am working on giving the user the ability to specify a Core Data instance (sqllite) to open, save, backup, etc. So far the user can create a new project - the program will create a “bundle/package” folder at the location chosen by NSSavePanel. Inside the bundle are the SQLLite files needed by Core Data, and open that Core Data instance and display the user interface.

To do this requires that it delays the creation of the CoreData instance from the app startup until later when the users decides to open a recent project or create a new one. Each project will have its own set of SQLLite files in the bundle (it appears as a single file instead of a folder). 

I need to close one instance and then open/create a new one while the program is running.

I am making some progress, but it is complicated (to me - being a newbie). For instance, the standard method of initialization of the PersistenceController/PersistenceContainer at startup and placing those in the environment works well - it seems they are singletons and immutable. So, I am working on using a class that contains those and publishing it, then I can update them. 

What I am asking for is if ANYONE has any suggestions on best practices when an app switches open/close/create etc Core Data instances on the fly, I would appreciate it. If anyone has any links to projects that actually do this, it would be great to see what someone else came up with.

2      

Well, I have made some progress. I guess this is not something most peope do -

Here is my attempt at adding a new persistence store - other things have to be dealt with before and after this, but those seems to be working okay. This is where I am getting errors, and could use advice: The significance here, is that the program runs fine and I can and do use the new persistent store that has been created with no errors after these are reported. Any advice on why these two errors are occuring would be much appreciated.

    func NewPersistentStore() {

        guard let newURL = selectedRecentProject.item?.url.appendingPathComponent("Hermes.sqlite") else { return }
        guard let persistentStore = persistenceController.container.persistentStoreCoordinator.persistentStores.last else { return }

        persistenceController.saveContext()
        persistenceController.container.viewContext.reset()
        do
            {
                try persistenceController.container.persistentStoreCoordinator.remove(persistentStore)
                try persistenceController.container.persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                                                                                  configurationName: nil,
                                                                                                  at: newURL,
                                                                                                  options: nil)
            } catch { /* dealing with errors once it is working */ 

The above throws this error (512) which appears to mean I created a directory at this url and then attempted to open it as a file. Nope. It created the persistent store Hermes.sqlite:

2021-01-24 13:24:28.096179-0500 Hermes[70581:1550495] Metal API Validation Enabled ---> viewContext does not have changes 2021-01-24 13:24:53.770425-0500 Hermes[70581:1550495] [error] error: addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (512) CoreData: error: addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (512) CoreData: annotation: userInfo: CoreData: annotation: reason : Failed to create file; code = 2 CoreData: annotation: storeType: SQLite CoreData: annotation: configuration: (null) CoreData: annotation: URL: file:///Users/frank/Documents/Hermes6.hermes/Hermes.sqlite

        let description = NSPersistentStoreDescription(url: newURL)

        persistenceController.container.persistentStoreDescriptions = [description]
        persistenceController.container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Fatal error loading store: \(error.localizedDescription)")
            }
        })

        let projectFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Project")

        do {
            _ = try persistenceController.container.viewContext.fetch(projectFetch) as! [Project]
        } catch {
            fatalError("Failed to fetch projects: \(error)")
        }
    }

Then when it attempts to load the persistent store this error:

2021-01-24 13:25:12.501734-0500 Hermes[70581:1550495] [error] error: addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (512) CoreData: error: addPersistentStoreWithType:configuration:URL:options:error: returned error NSCocoaErrorDomain (512) CoreData: annotation: userInfo: CoreData: annotation: reason : Failed to create file; code = 2 CoreData: annotation: storeType: SQLite CoreData: annotation: configuration: (null) CoreData: annotation: URL: file:///Users/frank/Documents/Hermes6.hermes/Hermes.sqlite CoreData: annotation: options: CoreData: annotation: NSInferMappingModelAutomaticallyOption : 1 CoreData: annotation: NSMigratePersistentStoresAutomaticallyOption : 1 CoreData: annotation: <NSPersistentStoreCoordinator: 0x60000123cee0>: Attempting recovery from error encountered during addPersistentStore: Error Domain=NSCocoaErrorDomain Code=512 "The file couldn’t be saved." UserInfo={reason=Failed to create file; code = 2}

2      

You should look into using UIManagedDocument. From the docs:

"UIManagedDocument is a concrete subclass of UIDocument. When you initialize a managed document, you specify the URL for the document location. The document object then creates a Core Data stack to use to access the document’s persistent store using a managed object model from the app’s main bundle."

Sounds like exactly what you need.

2      

Whoa!

I thought the document template couldn't use CoreData?

I will look into this, thank you.

BTW: This is on MacOS not iOS, but I expect they are close enough for me to figure it out.

Thank you again.

2      

It looks like what I want is NSPersistentDocument. The down side is it doesn't use the SwiftUI App lifecycle - yet. But, I think I can live with that for now.

I guess I am going to be learning stuff tomorrow - thanks alot.

2      

Okay, I don't think I want to go down the NSPersistentDocument path. While it seems to be designed to do what I want, it appears to be an abandoned step child. It does not support the newer SwiftUI app life cycle, etc. And there are very few complimentary comments around the web about it - seems to be a bit "flakey".

So, I went back and figured out what I am doing wrong and it turned out to be failry simple.

When you attempt to change to a new persistent store, full path to the store file must exist. The store file itself doesn't have to exist - the "container.loadPersistentStores()" will create a missing store for you. But, if the folder or any part of the path containing that store does not exist the load will throw some pretty scary looking errors. It does recover (which it never annouces with the flourish of errors) but it will work if you ignore the errors.

However, I don't like errors or warnings - so. After a bit of noodling, adding the createDirectory below solves the issue for me, with no more errors.

I need to make it a bit more robust - fix the fake try? and test to make sure the path is valid and create any missing parts. But, the basic's here work for now.

        let storeDirectory = NewProjectDirectory()!
        try? FileManager.default.createDirectory(at: storeDirectory, withIntermediateDirectories: true, attributes: nil)

        let storeURL = storeDirectory.appendingPathComponent("Hermes.sqlite")
        let description = NSPersistentStoreDescription(url: storeURL)

        container.persistentStoreDescriptions = [description]

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.