Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data with pre-filled .sqlite (Swift3)

currently I'm working on a Swift3 / iOS10 update of an existing iOS9 App which stores about 10.000 charging points for electric vehicles across europe. Up to now I always shipped the Application with a pre-filled database (.sqlite, .sqlite-shm, .sqlite-wal files from the .xcappdata bundle), but with the current Version Apple is introducing the NSPersistentContainer Class, which makes it a bit more complicated. In my AppDelegate Class I'm instantiating my NSPersistentContainer object and passing it to a lazy var like it's done by Apple in every example code:

   lazy var stationDataPersistentContainer: NSPersistentContainer = {  
     let fileMgr = FileManager.default
     let destinationModel = NSPersistentContainer.defaultDirectoryURL()
     if !fileMgr.fileExists(atPath: destinationModel.appendingPathComponent("StationData.sqlite").path) {
         do {
             try fileMgr.copyItem(at: URL(fileURLWithPath: Bundle.main.resourcePath!.appending("/StationData.sqlite")), to: destinationModel.appendingPathComponent("/StationData.sqlite"))
             try fileMgr.copyItem(at: URL(fileURLWithPath: Bundle.main.resourcePath!.appending("/StationData.sqlite-shm")), to: destinationModel.appendingPathComponent("/StationData.sqlite-shm"))
             try fileMgr.copyItem(at: URL(fileURLWithPath: Bundle.main.resourcePath!.appending("/StationData.sqlite-wal")), to: destinationModel.appendingPathComponent("/StationData.sqlite-wal"))
         } catch {
             //
         }
     } else {
         //
     }
     /* 
     The persistent container for the application. This implementation 
     creates and returns a container, having loaded the store for the 
     application to it. This property is optional since there are legitimate 
     error conditions that could cause the creation of the store to fail. 
     */
    let container = NSPersistentContainer(name: "StationData")  
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in  
         if let error = error as NSError? {  
             /* 
              * Typical reasons for an error here include: 
              * The parent directory does not exist, cannot be created, or disallows writing. 
              * The persistent store is not accessible, due to permissions or data protection when the device is locked. 
              * The device is out of space. 
              * The store could not be migrated to the current model version. 
              * Check the error message to determine what the actual problem was. 
              */  
             fatalError("Unresolved error \(error), \(error.userInfo)")  
        }
    })  
    return container  
    }()

In the iOS9 version im copying the files to the apropriate directory, like you can see in the following code example:

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {  
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)  
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("StationData.sqlite")  
    let fileMgr = NSFileManager.defaultManager()  
    if !fileMgr.fileExistsAtPath(url.path!) {  
         do {  
              try fileMgr.copyItemAtPath(NSBundle.mainBundle().pathForResource("StationData", ofType: "sqlite")!, toPath: self.applicationDocumentsDirectory.URLByAppment("StationData.sqlite").path!)  
              try fileMgr.copyItemAtPath(NSBundle.mainBundle().pathForResource("StationData", ofType: "sqlite-shm")!, toPath: self.applicationDocumentsDirectory.URLByAppendingPathComponent("StationData.sqlite-shm").path!)  
              try fileMgr.copyItemAtPath(NSBundle.mainBundle().pathForResource("StationData", ofType: "sqlite-wal")!, toPath: self.applicationDocumentsDirectory.URLByAppendingPathComponent("StationData.sqlite-wal").path!)  
         } catch {  
              //  
         } do {  
              try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url,  
                                                       options: [NSMigratePersistentStoresAutomaticallyOption:true, NSInferMappingModelAutomaticallyOption:true])  
         } catch {  
              //  
         }  
    } else {  
         //  
    }  
    return coordinator
}() 

For a number of days I have tried to move the files to the proper directory which is returned by NSPersistentContainer.defaultDirectoryURL() -> URL, but everytime I get an error, that the file already exists because my stationDataPersistentContainer is already initialized and so the NSPersistentContainer had enough time to generate the sqlite* files. Even if I try to copy the files and initialize the stationDataPersistentContainer in an overwritten init() function I could not get this right. Is there anything I'm missing or overlooking in the documentation? Which is the best/right/appropriate way to copy existing data on installation of an App into coredata.

Appendix:

Just for your Information, I could also store the JSON-Files, which I get from my API into the Documents directory and run the JSON-parser, but this needs a lot of ressources and especially time! (This question is also posted on the Apple Developers Forum and waiting for approval)

like image 452
cr0ss Avatar asked Feb 06 '23 05:02

cr0ss


2 Answers

This is how I do it:

lazy var persistentContainer: NSPersistentContainer = {

         let container = NSPersistentContainer(name: "app_name")

        let seededData: String = "app_name"
        var persistentStoreDescriptions: NSPersistentStoreDescription

        let storeUrl = self.applicationDocumentsDirectory.appendingPathComponent("app_name.sqlite")

        if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
            let seededDataUrl = Bundle.main.url(forResource: seededData, withExtension: "sqlite")
            try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)

        }

        print(storeUrl)


        container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeUrl)]
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error {

                fatalError("Unresolved error \(error),")
            }
        })

         return container


    }()
like image 77
George Asda Avatar answered Feb 21 '23 04:02

George Asda


The simplest solution is to not use NSPersistentContainer - it's only a convenience for taking away the stack boilerplate, and if you're updating an already working app, you could just leave your code as it is.

If you want to migrate to NSPersistentContainer, then move the files before you have called loadPersistentStores - it's at that point that the SQL files are first created.

Note that NSPersistentContainer may not be using the documents directory - out of the box on the iOS simulator it uses the application support directory. If using the documents directory is important to you, then you have to subclass NSPersistentContainer and override the defaultDirectoryURL class method, or provide an NSPersistentStoreDescription to the NSPersistentContainer which tells it where to store the data.

like image 35
jrturton Avatar answered Feb 21 '23 04:02

jrturton