skip to Main Content

Here is how I have defined my CoreDataManager

class CoreDataManager {
    static var shared = CoreDataManager()
    
    private let container: NSPersistentContainer
    
    lazy var defaultContext : NSManagedObjectContext = {
        let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        context.parent = rootContext
        context.setupDefaultContext()
        context.obtainPermanentIdsBeforeSaving()
        return context
    }()
    
    lazy var rootContext : NSManagedObjectContext = {
        let context = self.container.viewContext
        context.obtainPermanentIdsBeforeSaving()
        context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
        return context
    }()
    
    private let options: NSPersistentStoreDescription = {
        let options = NSPersistentStoreDescription()
        options.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
        options.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
        return options
    }()
    
    private let url: URL = {
        let containerUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: SharedGroupName)!
        let url = containerUrl.appendingPathComponent("MyApp.sqlite")
        return url
    }()
    
    init() {
        container = NSPersistentContainer(name: "MyApp")
        container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: url), options]
        container.loadPersistentStores { _, error in
            if let error { fatalError(error.localizedDescription) }
        }
    }
    
    func replaceDatabase() {
        if let oldUrl = container.persistentStoreDescriptions.first?.url {
            let coordinator = container.persistentStoreCoordinator
            coordinator.performAndWait {
                if let newUrl = Bundle.main.url(forResource: "MyApp", withExtension: ".sqlite") {
                    do {
                        try coordinator.replacePersistentStore(at: oldUrl, withPersistentStoreFrom: newUrl, type: .sqlite)
                    } catch {
                        print("❌ error (error)")
                    }
                }
            }
        }
    }
}

This is how I call it in code:

private func replaceDatabaseOnce() {
    if !UserDefaults.bool(forKey: "Done") {
        let manager = CoreDataManager.shared
        manager.replaceDatabase()
        UserDefaults.set(object: true, forKey: "Done")
    }
}

And when I try to fetch anything AFTER it is replaced, it doesn’t work. It look like database is empty. But it changes when I relaunch the app. Then everything is fine. Replaced database with previous launch is working fine. Is there a way to make it working WITHOUT relaunching an app?

2

Answers


  1. Chosen as BEST ANSWER

    All I had to to was to remove all current stores and load it again:

    for store in container.persistentStoreCoordinator.persistentStores {
        try container.persistentStoreCoordinator.remove(store)
    }
    
    container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: url), options]
    container.loadPersistentStores { _, error in
        if let error { fatalError(error.localizedDescription) }
    }
    

  2. The replacePersistentStore call only deals with copying files. It replaces one persistent store’s files with other ones. It doesn’t tell the container to start using a different store. But when you relaunch the app, the files are in place so they get used.

    To change stores on the fly you would need to call addPersistentStore for the store. It’s not that simple, though, because once you’ve moved the files around, literally everything from Core Data that you previously loaded is now invalid and could cause crashes if you use them. You need to re-create every context, re-fetch every object, etc. If you do this you should really discard your persistent container, create a new one, and start with new contexts and fetched objects.

    A big problem with replacePersistentStore is that it’s almost totally undocumented, so it can be hard to tell what it’s supposed to do. I wrote a blog post about researching this function to discover how it works.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search