更改对象模型 - Swift SDK
注意
修改同步 Realm 的模式属性
以下页面演示了如何修改本地 Realm 的模式属性。 了解如何修改同步 Realm 的模式属性。
概述
更新对象模式时,必须递增模式版本并执行迁移。
提示
另请参阅:
本页面提供 Swift 和 Objective-C 的一般迁移示例。如果您将 Realm 与 SwiftUI 结合使用,请参阅 SwiftUI 特定的迁移示例。
如果模式更新操作添加了可选属性或删除了属性,则 Realm 可自动执行迁移。您只需递增 schemaVersion
。
对于更复杂的架构更新,您还必须在 migrationBlock
中手动指定迁移逻辑。该操作可能包括如下更改:
添加必须使用默认值填充的必要属性
合并字段
重命名字段
更改字段的类型
从对象转换为嵌入式对象
提示
在开发期间绕过迁移
在开发或调试应用程序时,您可能更愿意删除而不是迁移 Realm。当模式不匹配需要迁移时,使用deleteRealmIfMigrationNeeded标志自动删除数据库。
切勿将此标记设为 true
的应用发布到生产环境。
模式版本
模式版本可标识 Realm 模式在某个时间点的状态。Realm 会跟踪每个 Realm 的模式版本,并使用该信息将每个 Realm 中的对象映射到正确的模式。
模式版本是指打开 Realm 时可包含在 Realm 配置中的整数。如果客户端应用程序在打开 Realm 时未指定版本号,则该 Realm 默认采用 0
版本。
重要
单调递增版本
迁移必须将 Realm 更新到更高的模式版本。如果客户端应用程序打开的 Realm 的模式版本低于该 Realm 的当前版本,或是指定的模式版本与该 Realm 的当前版本相同但包含不同对象模式,Realm 则会引发错误。
迁移
本地迁移是针对不会自动与另一个 Realm同步的 Realm 的迁移。 本地迁移可以访问现有的 Realm 模式、版本和对象,并定义以增量方式将 Realm 更新到新模式版本的逻辑。 要执行本地迁移,您必须指定高于当前版本的新模式版本,并在打开过时 Realm 时提供迁移函数。
在 iOS 中,您可以使用手动迁移来更新底层数据,从而反映模式更改。此类手动迁移期间,当您在模式中添加或删除属性时可定义新属性和已删除属性。
自动更新模式
添加属性
Realm 可自动迁移添加的属性,但您在执行这些更改时必须指定更新后的模式版本。
注意
Realm 不会自动为新的必需属性设置值。您必须使用迁移区块为新的必需属性设置默认值。对于新的可选属性,现有记录可具有 null 值。这表示在添加可选属性时无需迁移区块。
例子
使用 1
模式版本的 Realm 具有 Person
对象类型,而该对象类型具有名字、姓氏和年龄属性:
// In the first version of the app, the Person model // has separate fields for first and last names, // and an age property. @interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; @property int age; @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"firstName", @"lastName", @"age"]; } @end
// In the first version of the app, the Person model // has separate fields for first and last names, // and an age property. class Person: Object { var firstName = "" var lastName = "" var age = 0 }
开发者将确定 Person
类需要 email
字段并更新模式。
// In a new version, you add a property // on the Person model. @interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; // Add a new "email" property. @property NSString *email; // New properties can be migrated // automatically, but must update the schema version. @property int age; @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"firstName", @"lastName", @"email", @"age"]; } @end
// In a new version, you add a property // on the Person model. class Person: Object { var firstName = "" var lastName = "" // Add a new "email" property. var email: String? // New properties can be migrated // automatically, but must update the schema version. var age = 0 }
Realm 会自动迁移该 Realm 以符合更新后的 Person
模式。但开发者必须将 Realm 的模式版本设为 2
。
// When you open the realm, specify that the schema // is now using a newer version. RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; // Set the new schema version config.schemaVersion = 2; // Use this configuration when opening realms [RLMRealmConfiguration setDefaultConfiguration:config]; RLMRealm *realm = [RLMRealm defaultRealm];
// When you open the realm, specify that the schema // is now using a newer version. let config = Realm.Configuration( schemaVersion: 2) // Use this configuration when opening realms Realm.Configuration.defaultConfiguration = config let realm = try! Realm()
删除属性
要从架构中删除一个属性,请从对象的类中删除该属性,并设置 Realm 配置对象的 schemaVersion
。删除属性不会影响现有的对象。
例子
使用 1
模式版本的 Realm 具有 Person
对象类型,而该对象类型具有名字、姓氏和年龄属性:
// In the first version of the app, the Person model // has separate fields for first and last names, // and an age property. @interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; @property int age; @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"firstName", @"lastName", @"age"]; } @end
// In the first version of the app, the Person model // has separate fields for first and last names, // and an age property. class Person: Object { var firstName = "" var lastName = "" var age = 0 }
开发者确定 Person
不需要 age
字段并更新架构。
// In a new version, you remove a property // on the Person model. @interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; // Remove the "age" property. // @property int age; // Removed properties can be migrated // automatically, but must update the schema version. @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"firstName", @"lastName"]; } @end
// In a new version, you remove a property // on the Person model. class Person: Object { var firstName = "" var lastName = "" // Remove the "age" property. // @Persisted var age = 0 // Removed properties can be migrated // automatically, but must update the schema version. }
Realm 会自动迁移该 Realm 以符合更新后的 Person
模式。但开发者必须将 Realm 的模式版本设为 2
。
// When you open the realm, specify that the schema // is now using a newer version. RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; // Set the new schema version config.schemaVersion = 2; // Use this configuration when opening realms [RLMRealmConfiguration setDefaultConfiguration:config]; RLMRealm *realm = [RLMRealm defaultRealm];
// When you open the realm, specify that the schema // is now using a newer version. let config = Realm.Configuration( schemaVersion: 2) // Use this configuration when opening realms Realm.Configuration.defaultConfiguration = config let realm = try! Realm()
提示
SwiftUI 开发者在添加或删除属性时可能会看到错误“需进行迁移”。此问题与 SwiftUI 中的生命周期有关。对视图进行布局,然后 .environment
修饰符会设置该配置。
要解决这些情况下出现的迁移错误,请将 Realm.Configuration(schemaVersion: <Your Incremented Version>)
传递给 ObservedResults
构造函数。
手动迁移模式
对于更复杂的模式更新,Realm 需将给定对象的旧实例手动迁移到新模式。
为属性重命名
要在迁移期间重命名属性,请使用Migration.renameProperty(onType:from:to:)方法。
Realm 会在重命名操作期间应用所有新的为空性 (nullability) 或索引设置。
例子
在迁移块中将age
重命名为yearsSinceBirth
。
RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; config.schemaVersion = 2; config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 2) { // Rename the "age" property to "yearsSinceBirth". // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`. [migration renamePropertyForClass:[Person className] oldName:@"age" newName:@"yearsSinceBirth"]; } };
let config = Realm.Configuration( schemaVersion: 2, migrationBlock: { migration, oldSchemaVersion in if oldSchemaVersion < 2 { // Rename the "age" property to "yearsSinceBirth". // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`. migration.renameProperty(onType: Person.className(), from: "age", to: "yearsSinceBirth") } })
修改属性
提示
如果需要迁移,您可以使用 deleteRealmIfMigrationNeeded 方法删除该域。在需要快速迭代且不想执行迁移时,此功能在开发期间非常有用。
要定义自定义迁移逻辑,请在 打开 Realm 时设置 配置 的 迁移区块 属性。
迁移区块接收可用于执行迁移的迁移对象。您可以使用迁移对象的enumerateObjects(ofType:_:)方法遍历并更新 Realm 中给定 Realm 类型的所有实例。
例子
使用 1
模式版本的 Realm 具有 Person
对象类型,而该对象类型具有针对名字和姓氏的单独字段:
// In the first version of the app, the Person model // has separate fields for first and last names, // and an age property. @interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; @property int age; @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"firstName", @"lastName", @"age"]; } @end
// In the first version of the app, the Person model // has separate fields for first and last names, // and an age property. class Person: Object { var firstName = "" var lastName = "" var age = 0 }
开发者将确定 Person
类应使用 fullName
组合字段而不是单独的 firstName
和 lastName
字段,并更新模式。
为了迁移 Realm 以符合更新的 Person
架构,开发者将 Realm 的架构版本设置为 2
,并定义一个迁移函数以根据现有的 firstName
和 lastName
属性设置 fullName
值。
// In version 2, the Person model has one // combined field for the full name and age as a Int. // A manual migration will be required to convert from // version 1 to this version. @interface Person : RLMObject @property NSString *fullName; @property int age; @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"fullName", @"age"]; } @end
RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; // Set the new schema version config.schemaVersion = 2; config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 2) { // Iterate over every 'Person' object stored in the Realm file to // apply the migration [migration enumerateObjects:[Person className] block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { // Combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } }; // Tell Realm to use this new configuration object for the default Realm [RLMRealmConfiguration setDefaultConfiguration:config]; // Now that we've told Realm how to handle the schema change, opening the realm // will automatically perform the migration RLMRealm *realm = [RLMRealm defaultRealm];
// In version 2, the Person model has one // combined field for the full name and age as a Int. // A manual migration will be required to convert from // version 1 to this version. class Person: Object { var fullName = "" var age = 0 }
// In application(_:didFinishLaunchingWithOptions:) let config = Realm.Configuration( schemaVersion: 2, // Set the new schema version. migrationBlock: { migration, oldSchemaVersion in if oldSchemaVersion < 2 { // The enumerateObjects(ofType:_:) method iterates over // every Person object stored in the Realm file to apply the migration migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // combine name fields into a single field let firstName = oldObject!["firstName"] as? String let lastName = oldObject!["lastName"] as? String newObject!["fullName"] = "\(firstName!) \(lastName!)" } } } ) // Tell Realm to use this new configuration object for the default Realm Realm.Configuration.defaultConfiguration = config // Now that we've told Realm how to handle the schema change, opening the file // will automatically perform the migration let realm = try! Realm()
随后,开发者将确定 age
字段的类型应为 String
而不是 Int
,并更新模式。
为迁移 Realm 以符合更新后的 Person
模式,开发者会将此 Realm 的模式版本设为 3
,并向迁移函数添加一个条件,以便该函数定义如何从任一先前版本迁移到新版本。
// In version 3, the Person model has one // combined field for the full name and age as a String. // A manual migration will be required to convert from // version 2 to this version. @interface Person : RLMObject @property NSString *fullName; @property NSString *age; @end @implementation Person + (NSArray<NSString *> *)requiredProperties { return @[@"fullName", @"age"]; } @end
RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; // Set the new schema version config.schemaVersion = 3; config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) { if (oldSchemaVersion < 2) { // Previous Migration. [migration enumerateObjects:[Person className] block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } if (oldSchemaVersion < 3) { // New Migration [migration enumerateObjects:[Person className] block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { // Make age a String instead of an Int newObject[@"age"] = [oldObject[@"age"] stringValue]; }]; } }; // Tell Realm to use this new configuration object for the default Realm [RLMRealmConfiguration setDefaultConfiguration:config]; // Now that we've told Realm how to handle the schema change, opening the realm // will automatically perform the migration RLMRealm *realm = [RLMRealm defaultRealm];
// In version 3, the Person model has one // combined field for the full name and age as a String. // A manual migration will be required to convert from // version 2 to this version. class Person: Object { var fullName = "" var age = "0" }
// In application(_:didFinishLaunchingWithOptions:) let config = Realm.Configuration( schemaVersion: 3, // Set the new schema version. migrationBlock: { migration, oldSchemaVersion in if oldSchemaVersion < 2 { // Previous Migration. migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in let firstName = oldObject!["firstName"] as? String let lastName = oldObject!["lastName"] as? String newObject!["fullName"] = "\(firstName!) \(lastName!)" } } if oldSchemaVersion < 3 { // New Migration. migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in // Make age a String instead of an Int newObject!["age"] = "\(oldObject!["age"] ?? 0)" } } } ) // Tell Realm to use this new configuration object for the default Realm Realm.Configuration.defaultConfiguration = config // Now that we've told Realm how to handle the schema change, opening the file // will automatically perform the migration let realm = try! Realm()
提示
线性迁移
避免在迁移区块中进行嵌套或以其他方式跳过 if (oldSchemaVersion < X)
语句。此举可确保无论客户端从哪个模式版本启动,所有更新操作均可按正确顺序进行应用。其目标是定义迁移逻辑,而该逻辑可将所有已过时模式版本中的数据进行转换,从而匹配当前模式。
从对象转换为嵌入式对象
嵌入式对象无法独立于父对象而存在。将某一对象更改为嵌入式对象时,迁移区必须确保每个嵌入式对象均有一个指向父对象的反向链接。缺少反向链接或有多个反向链接会导致以下异常:
At least one object does not have a backlink (data would get lost).
At least one object does have multiple backlinks.