Friday, August 19, 2022

iOS Core Data Database Update

The Core Data database is the most commonly used as a data store for iOS apps. Nobody could design the database once and never change it again. I occasionally need to make some changes or update my Core Data database.

When the database scheme is changed, for example, adding a new entity, removing an entity, or changing entity fields, how could such kinds of changes affect your app? Would your app users lose all the data if any changes are made?

If you don't set up your database correctly, you may cause users frustration or pain. Fortunately, Apple provides a very solid and complete core data framework to allow you to migrate database changes during your app's life time without losing any data. In most cases, you would not need to do anything in particular. In some cases, you may need to write some codes to update data since you know what should be done.

Here are some notes regarding this issue.

Set the Correct Core Data Store Options

For the time being, in iOS 10.*, a Core Data store is created and managed by means of NSPersistentContainer. At the time this container is created, some critical options have to be set so that future updates or changes will be migrated smoothly without losing data.

The following is an example of how to set up the container with important options. If you don't set those, any changes to the database store would cause data loss because of a totally new schema.

  1. let storeDesription = NSPersistentStoreDescription(url: url)
  2. storeDesription.shouldInferMappingModelAutomatically = true
  3. storeDesription.shouldMigrateStoreAutomatically = true
  4. container.persistentStoreDescriptions = [storeDesription]
  5. container.loadPersistentStores() {
  6.    (storeDescription, error) in
  7.    if let error = error as NSError? {
  8.        // Handle errors here
  9.    }
  10. }

Add a New Version of the Database

In Xcode 9.4, if you need to change your database schema, don't make the change directly to the database model. Instead, add a new version to your database model and make the new version the active model.

From the menu of Editor->Add Model Version...,

Accepting the recommended model name, a new model version will be added.

In the right-hand inspector, add a new model identifier and select the new version as the current model version.

In this way, you can carry the previous database schema as-it-is to the next new version and make changes to the new version.

In most cases, all your data will be kept in the new database without any loss. Even though you have to test your app after your update to make sure it will keep or present previous database data smoothly.

Add Your Customized Updates

If you need to update your data as you know what the changes are and how to set up data correctly, you can do it at your app startup time.

For example, here is my practice of updating data at the app delegate class:

  1. @UIApplicationMain
  2. class AppDelegate : UIResponder, UIApplicationDelegate {
  3.  func application(_ application: UIApplication,
  4.    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
  5.    {
  6.       // Override point for customization after application launch.
  7.       MyUserDefaults.shared.setupDefaults()
  8.       AppDatabase.shared.updateDatabase()
  9.       return true
  10.    }
  11. ...
  12. }

I use UserDefaults, i.e., app settings, as a place to save app and database versions and load those versions at the app's startup time. Then in the updateDatabase() method, I'll check if the previous app and database versions are older than a given version. If so, then some updates will be made so that the data will be kept up to date. Here is my example case:

  1. class AppDatabase {
  2.  static var shared: AppDatabase {
  3.    struct local {
  4.      static let instance = AppDatabase()
  5.    }
  6.    return local.instance
  7.  }
  8.  func updateDatabase() {
  9.     guard VersionManager.shared.isOlderThan(.database_v1_2_0) else { return }
  10.     cleanUpDataBase()      // do some cleanup missed in old db
  11.     updateSortProperties() // set sort values for the new fields
  12.  }
  13.  ...
  14. }