Search code examples
iosswiftcore-data

CoreData -> Multiple data stores based on some value


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?


Solution

  • 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)
        }
    }