I have an odd need, and I suspect the answer is going to be "it's not meant to work that way", but here goes (and no, I haven't tried this yet) - I need to be able to hit various data stores in CoreData based on the app user's login. Think of an on-call phone that gets handed around to multiple people, each having their own login. So, if Bob has the phone and logs in, it creates/uses a CoreData data store unique to Bob, then when Bob logs out and hands the phone off to Sandy, she logs in and same deal.
I know you provide the name of the CoreData data store in the CoreData initialization code, but I've read a number of articles where people say CoreData gets squirrely if that name isn't the name of the app. Not sure I believe that is true (or STILL true...?), but am I just barking up the wrong tree here?
Thoughts?
It is not required for the Core Data store's name to match the name of the app. In fact, it is possible to have multiple data stores with different names for a single app.
To achieve this, you need to create the NSPersistentContainter
providing the data store name and the managed object model.
That is, instead of NSPersistentContainer(name: storeName)
initialiser you need the NSPersistentContainer(name: storeName, managedObjectModel: managedObjectModel)
.
The challenging part is to obtain the NSManagedObjectModel
for the current version of the data model, but the snippet below will show you how to do that:
Struct PersistenceController {
let container: NSPersistentContainer
init(userName: String,
inMemory: Bool = false) {
// Both the store and data model can be any string, for the sake of simplicity lets
// consider your app's name
let yourAppName = "YourAppName"
// Append the userName to your data store name
let storeName = "\(yourAppName)-\(userName)"
// This must be the same name as the data model file in your project.
// Typically is the same as the app's name
let dataModelName = yourAppName
// Here you get the managedObjectModel to create the persistent container
guard let managedObjectModel = NSManagedObjectModel(name: dataModelName) else {
fatalError("Cannot instantiate NSManagedObjectModel")
}
// When you create the persistent container specifying the name and the managed object model,
// you "decouple" the name of the store from the name of the data model:
container = NSPersistentContainer(name: storeName, managedObjectModel: managedObjectModel)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// Other code required for creating the persistent container goes here,
// including the creation of the mainContex and any background context you need.
}
}
extension NSManagedObjectModel {
convenience init?(name: String) {
let bundle = Bundle.main
let dataModelFolder = "\(name).momd" //Assuming your CoreData model is located in your project's roor directory
// You need to read the current model version from the VersionInfo.plist file, located in the datamodel folder
guard let versionPlistUrl = bundle.url(forResource: "VersionInfo",
withExtension: "plist",
subdirectory: dataModelFolder) else { fatalError("VersionInfo.plist doesn't exist") }
// Here you parse the VersionInfo.plist file to a Dictionary and get the current version name:
let versionPlist = NSDictionary(contentsOf: versionPlistUrl)
let currentVersion = versionPlist?.object(forKey: "NSManagedObjectModel_CurrentVersionName") as? String
// You need the current version model URL to instantiate the managed object model:
guard let currentModelVersionURL = bundle.url(forResource: currentVersion,
withExtension: "mom",
subdirectory: dataModelFolder) else { fatalError("Model not found") }
// Initialise the model with the url defined above
self.init(contentsOf: currentModelVersionURL)
}
}