“文档” 菜单
文档首页
/ /
Atlas Device SDKs
/ /

更改对象模型 - 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 {
@Persisted var firstName = ""
@Persisted var lastName = ""
@Persisted 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 {
@Persisted var firstName = ""
@Persisted var lastName = ""
// Add a new "email" property.
@Persisted var email: String?
// New properties can be migrated
// automatically, but must update the schema version.
@Persisted 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 {
@Persisted var firstName = ""
@Persisted var lastName = ""
@Persisted 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 {
@Persisted var firstName = ""
@Persisted 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 {
@Persisted var firstName = ""
@Persisted var lastName = ""
@Persisted var age = 0
}

开发者将确定 Person 类应使用 fullName 组合字段而不是单独的 firstNamelastName字段,并更新模式。

为了迁移 Realm 以符合更新的 Person 架构,开发者将 Realm 的架构版本设置为 2,并定义一个迁移函数以根据现有的 firstNamelastName 属性设置 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 {
@Persisted var fullName = ""
@Persisted 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 {
@Persisted var fullName = ""
@Persisted 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.

提示

请查看 realm-swift 存储库上的其他迁移示例。

后退

支持的类型

来年

使用 Device Sync 对数据建模