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
All I had to to was to remove all current stores and load it again:
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.