First of all, I’d like to thank D for his recommendation. Many friends have paid attention to me. Since I just started to write technical articles, I feel very guilty for not writing anything real.

Today, I discussed some details of Core Data migration. I read a lot of materials, conducted repeated verification, and filled in a lot of holes.

The scope of this article is limited to auto-triggered custom migrations, which will be covered later.

Data backup and restoration

Before the migration test, back up the original data:

  • Open Xcode -> Windows -> Devices…
  • Select the App you want to back up, click the ⚙️ icon, and selectDownload container.
  • SQLite files are in this directory:xxx.xcappdata/AppData/Library/Application Support/xxx.sqlite
  • If you need to restore data, under the same Xcode menu, selectReplace Container.

Create a new data model version

  • Select the data model filexxx.xcdatamodeld, open the menuEditor -> Add Model Version, add a new version as prompted.
  • In file properties on the right side of Xcode, selectModel VersionIs the newly created version.
  • Edit the required changes in the new model file. If, as before, the two versions are formally the same (same records, attributes, types) but fundamentally different, they need to be found in the Xcode panel on the different entities or attributes in order to trigger the migration automaticallyVersioning -> Hash Modifier, fill in an arbitrary name so that the runtime will think the two versions are different. If there are formal differences, this field may not be filled in.
  • If a new version of the model file does not meet the requirements and you want to delete the rebuild, you cannot delete the new version directly in Xcode, you can do this directly in Finder by right clickingDisplay package contentsAccess the internal and delete the corresponding version file. But the file will still be in Xcode and shown in red, open the project directoryproject.pbxprojAccording to the model file name search, delete the corresponding line.

Building a mapping model

  • To make a custom migration, you must have a mapping model, which tells the App how to transfer the data from the old model to the new model. This step must be performed after the new data model version is finalized, otherwise an error message will be displayed indicating that the mapping model cannot be found if it is not run consistently. If you find that the model version needs to be changed back, it is best to delete and recreate the mapping model.

  • If there are any changes in the Model, including modifying the Hash Modifier, you can also Refresh the Mapping Model by selecting the Mapping Model file and going to The Menu Editor -> Refresh Data Models. Right click on Open As -> Quick Look and click on Open As -> Mapping Model again to refresh the display.

  • The SQlite file in the installed App will record the hash value of the model version, which is inconsistent with the value calculated by the current running model version. The reason for this is that the model version must have been modified. Any changes made to the model file in the Xcode editor, including the above Hash Modifier and so on, will result in a different Hash value and the App will assume that it has found a new version of the model file, but the existing mapping model does not match. At this point, only the model file can be restored to be consistent with the version installed in the App. If you do make these changes, you can only honestly create a new version of the model and a new mapping model.

  • Value Expression in the mapping model is actually a Value of the NSExpression type, so it is written according to the rules of NSExpression. It can perform simple math operations (numeric type attributes), such as $source.xxx + 10; You can also use KeyPath similar to that in KVC, such as $source.xxx.yyy, but be careful about using KeyPath. XXX must be a subclass of NSObject, and if XXX is a set type, You can also use the collection operator in a similar way to KVC’s collection operations.

    One thing to note about the type of YYy is that if the XXX property type to be mapped is of Data or Transformable type and the actual store is a custom class, THEN YYy can only reference stored properties or instance methods in XXX (without parameters, If one of the calculated properties is a property, then we should provide a getProperty() method. If one of the calculated properties is a property, then we should provide a getProperty() method. Then use the $source.xxx.getProperty reference.

  • Another way to map properties is to use FUNCTION(object, selector, parameter…). , similar to objc_msgSend syntax, where object represents the object that can be used in the migration process. For example, the following keys are the default keys of Core Data, selector represents the method pointer owned by object, and parameter is the specific parameter:

// The default key of Core Data
NSMigrationManagerKey: $manager
NSMigrationSourceObjectKey: $source
NSMigrationDestinationObjectKey: $destination
NSMigrationEntityMappingKey: $entityMapping
NSMigrationPropertyMappingKey: $propertyMapping
NSMigrationEntityPolicyKey: $entityPolicy
Copy the code

You have to be very careful how selector is written. In Swift if your method is combine(firstName:String, lastName: String), then will be written in FUNCTION combineWithFirstName: lastName:, middle to add “With”, should be similar in Objc. If the first parameter name is with or from (lowercase), or if the method name ends with or from (uppercase), then it is not necessary to add with. Otherwise, the program will automatically add with before the first parameter name at compile time. If you are not sure how to write the name of the method, you can print it on playground, as in:

class Test {
    @objc func combine(firstName: String, lastName: String) -> String {
        return firstName + lastName
    }
}

print(#selector(Test.combine(firstName:lastName:))) // combineWithFirstName:lastName:
Copy the code
  • If none of the above does the mapping, customize the migration strategy.

Custom migration policies

  • This policy is really only for the mapping process, and you need to customize the migration policy class when the mapping requirements are not met in the Xcode editor.
  • createNSEntityMigrationPolicySubclass, by rewriting the method in the class, to achieve a custom mapping, such as the following code to complete the mapping process of the target object, all the maps not defined in the code will be found in the mapping model, so only write parts that are not directly copied:
final class V1To2Policy: NSEntityMigrationPolicy {
    override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
    {
        try super.createDestinationInstances(
            forSource: sInstance, in: mapping, manager: manager)
        guard let xxx = sInstance.value(forKey: "xxx") else { return } // Get the original attribute value
        let newValue = .... // Calculate the mapped attribute value
        guard let newItem = manager.destinationInstances( // Get the mapped new object
            forEntityMappingName: "XXXToXXX".// Note that this is consistent with the entity mapping name in the mapping model
            sourceInstances: [sInstance]).first else { return }
        newItem.setValue(newValue, forKey: "xxx") // Set the property value of the new object}}Copy the code
  • Finally, the migration strategy will be defined and filled in the entity mapping in the mapping modelCustom PolicyIn the field, the policy name must be specified as follows:ModuleName.CustomPolicyClassNameFor example, the Target name you want to run isExampleAppTo customize the policy class nameCoreDataModelV1ToV2“, then finally fill inExampleApp.CoreDataModelV1ToV2. Another exception is if the Target name begins with a number, as in1ExampleApp, should actually fill in (1 instead of underscore) :_ExampleApp.CoreDataModelV1ToV2This might be something that Xcode does automatically. Not sureModuleNameFind a storyboard or xiB file and look at the sourcecustomModuleWhat is the value in the field, and this is your TargetModuleName.

Managed object subclass

  • After the data model changes, the managed object subclass also needs to change accordingly, in this case only according to the latest version of the model. But in the original model of entity attributes, or the migration strategy, if you use the original type or method, pay attention to keep, or read or migration will be submitted to the original Data type errors (and this is one aspect of excess compatible code, should try to avoid such a design, for example for entity attribute Data, Transformable type, Try not to store custom types directly. Instead, store basic types and their combinations, which can be converted to custom types by methods. .

Migration option Settings

  • Migration mark:NSPersistentContainerThere is a propertypersistentStoreDescriptionsOr,NSPersistentStoreCoordinatoraddPersistentStoreThere’s a methodoptionsOption, added in this optionMigration Options:
// iOS 10 or above
container.persistentStoreDescriptions[0].shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions[0].shouldInferMappingModelAutomatically = false

// iOS 10
let options = [NSMigratePersistentStoresAutomaticallyOption: true.NSInferMappingModelAutomaticallyOption: false]
do {
    try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
} catch {
    fatalError("Failed to add persistent store: \(error)")}Copy the code

There’s a lot more to talk about in Core Data, so take your time.


Please visit my website to read more articles.

Title: Zigzig-La_paupiette_Masque@unsplash