集合类型
Realm 有多种类型来表示对象组,而我们将其称为集合。集合是指包含零个或多个某种 Realm 类型实例的对象。Realm 集合具有同质性:集合中的所有对象均为同一类型。
您可以使用 Realm 的查询引擎对任意集合进行筛选和排序。集合为实时对象,因此它们始终会反映当前线程上 Realm 实例的当前状态。此外,还可通过订阅集合通知来侦听集合中的更改。
所有集合类型都符合 RealmCollection 协议。此协议继承自 CollectionType ,因此您可以像使用任何其他标准库集合一样使用Realm集合。
通过使用 RealmCollection 协议,您可以编写可对任意 Realm 集合进行操作的通用代码:
func operateOn<C: RealmCollection>(collection: C) { // Collection could be either Results or List print("operating on collection containing \(collection.count) objects") }
结果和分区结果
Swift SDK结果集合是一个表示从查询中检索到的对象的类。 结果集合表示查询操作的延迟计算结果。 结果是不可变的:您不能在结果集合中添加或删除元素。 结果具有确定其内容的关联查询。
Swift SDK还提供了SectionedResults ,这是一个类型安全的集合,其中包含 ResultsSection
作为其元素。 每个ResultSection都是一个结果集合,其中仅包含属于给定部分键的对象。
例如,包含联系人列表的应用程序可能会使用SectionedResults 来显示划分为多个节的联系人列表,其中每个节均包含名字以给定字母开头的所有联系人。键为“L”的 ResultsSection
将包含“Larry”、“Liam”和“Lisa”。
提示
集合即属性
Swift SDK 还提供多种集合类型,而您可将其用作个人数据模型中的属性:
LinkingObjects ,一个表示模型中反向关系的类。
Map ,一个类,表示具有唯一键的键值对关联数组。
AnyRealmCollection , 类型擦除的 类,它可以将调用转发到具体的 Realm 集合,例如 Results、List 或 LinkingObjects。
集合的实时性
与活动对象一样,Realm 集合通常也为实时集合:
实时结果集合始终会反映关联查询的当前结果。
实时列表始终会反映 Realm 实例关系的当前状态。
集合在两种情况下处于非实时状态:
集合为非托管集合。例如,尚未添加到 Realm 或已从 Realm 复制的 Realm 对象的 List 属性便处于非实时状态。
集合已被冻结。
与集合通知相结合时,实时集合可实现简洁的响应式代码。例如,假设您的视图会显示查询结果。您可在视图类中保留对结果集合的引用,然后按需读取结果集合,而不必刷新该集合或验证其是否处于最新状态。
重要
结果索引可能会发生变化
由于结果会自动更新自身,因此请勿存储集合中对象的位置索引或集合中对象的数量。存储的索引或数量值在您使用时可能已过期。
支持的属性类型
您可以使用以下类型来定义对象模型属性。
要了解如何将特定数据类型映射到 App Services 模式中的 BSON 类型,请参阅 Atlas App Services 文档中的数据模型映射。
另请参阅使用 Device Sync 对数据建模 - Swift SDK。
属性速查表
在版本 10.10.0 中进行了更改:@Persisted
属性声明语法
类型 | 必需 | Optional | ||
---|---|---|---|---|
bool |
|
| ||
Int、Int8、Int16、Int32、Int64 |
|
| ||
Float |
|
| ||
double |
|
| ||
字符串 |
|
| ||
数据 |
|
| ||
Date |
|
| ||
Decimal128 |
|
| ||
|
| |||
|
| |||
| 不适用 | |||
| 不适用 | |||
| 不适用 | |||
| 不适用 | |||
用户定义的对象 | 不适用 |
| ||
用户定义的 EmbeddedObject | 不适用 |
| ||
用户定义的 Enums |
|
|
CGFloat
属性不鼓励使用,因为此类型与平台无关。
要在 @Persisted
语法中将键值编码与用户定义的对象结合使用,请添加 @objc
属性:@Persisted @objc var myObject: MyClass?
设置默认值
使用 @Persisted
属性声明语法,在为以下属性设置默认值时可能会影响性能:
List
MutableSet
Dictionary
Decimal128
UUID
ObjectId
@Persisted var listProperty: List<Int>
和@Persisted var
listProperty = List<Int>()
都是有效的,并且在功能上等效。 但是,第二种声明会导致性能下降。
这是因为 List 是在创建父对象时创建的,而不是按需延迟创建的。对于大多数类型来说,这种差异太小了,因此无法测量。对于此处列出的类型,使用第二种声明样式时可能会对性能产生影响。
类型 | 必需 | Optional | ||
---|---|---|---|---|
布尔 |
|
| ||
整型 |
|
| ||
Float |
|
| ||
double |
|
| ||
字符串 |
|
| ||
数据 |
|
| ||
Date |
|
| ||
Decimal128 |
|
| ||
NSUUID |
|
| ||
|
| |||
| 不适用 | |||
| 不适用 | |||
| 不适用 | |||
用户定义的RLMObject | 不适用 |
| ||
用户定义的RLMEmbeddedObject | 不适用 |
|
此外:
整数类型
int
、NSInteger
、long
、long long
CGFloat
属性不鼓励使用,因为此类型与平台无关。
10.8.0 版本中的更改:RealmProperty
取代了 RealmOptional
类型 | 必需 | Optional | ||
---|---|---|---|---|
bool |
|
| ||
Int、Int8、Int16、Int32、Int64 |
|
| ||
Float |
|
| ||
double |
|
| ||
字符串 |
|
| ||
数据 |
|
| ||
Date |
|
| ||
Decimal128 |
|
| ||
|
| |||
|
| |||
| ||||
| ||||
| ||||
| 不适用 | |||
用户定义的对象 | 不适用 |
|
此外:
EmbeddedObject派生类型
您可以使用RealmProperty <T?>
来表示整数、双精度和其他可选类型。
CGFloat
属性不鼓励使用,因为此类型与平台无关。
Unique Identifiers
10.8.0 版新增功能:UUID
类型
ObjectId
为特定于 MongoDB 的 12 字节唯一值。UUID
为 16 字节的全局唯一值。您可为这两种类型创建索引,并将其中任一用作主键。
注意
为 @Persisted
UUID 或 ObjectId 属性声明默认值时,以下两种语法类型均有效:
@Persisted var value: UUID
@Persisted var value = UUID()
但是,第二种语法会导致性能降低。这是因为后者会创建一个新标识符,但每次从 Realm 读取对象时均不会使用该标识符,而前者仅会在需要时创建它们。
@Persisted var id: ObjectId
具有与 @objc dynamic
var _id = ObjectId.generate()
等效的行为。它们都会创建随机 ObjectId。
@Persisted var _id = ObjectId()
具有与 @objc
dynamic var _id = ObjectId()
等效的行为。它们均会创建用零初始化的 ObjectId。
大小限制
数据和字符串属性的容量不得超过 16 MB。要存储更大容量的数据,则可:
将此数据分解为 16 MB 的数据段,或者
将数据直接存储在文件系统上,并存储 Realm 中文件的路径。
如果您的应用尝试在单个属性中存储超过 16 MB 的数据,Realm 则会引发运行时异常。
为避免大小限制和性能影响,最好请勿将大型 Blob(例如图像和视频文件)直接存储在 Realm 中。相反,请将此文件保存到文件存储,并仅保留此文件的位置以及 Realm 中的所有相关元数据。
AnyRealmCollection
要将集合存储为属性或变量而无需知道具体的集合类型,Swift 的类型系统需要一种擦除类型的封装器,如 AnyRealmCollection:
class ViewController { // let collection: RealmCollection // ^ // error: protocol 'RealmCollection' can only be used // as a generic constraint because it has Self or // associated type requirements // // init<C: RealmCollection>(collection: C) where C.ElementType == MyModel { // self.collection = collection // } let collection: AnyRealmCollection<MyModel> init<C: RealmCollection & _ObjcBridgeable>(collection: C) where C.ElementType == MyModel { self.collection = AnyRealmCollection(collection) } }
可变集合
10.8.0 版本新增。
MutableSet集合表示包含不同值的对多关系。 MutableSet
支持以下类型(及其可选版本):
bool
数据
Date
Decimal128
double
Float
Int
Int8
Int16
Int32
Int64
对象
ObjectId
字符串
UUID
就像 Swift 的 Set ,MutableSet
是一种泛型类型,会根据其存储的类型进行参数化。与 原生 Swift 集合 不同 ,Realm 可变集是引用类型,而不是值类型(结构体)。
您只能在写入事务期间调用 MutableSets
更改方法。因此,如果将管理 Realm 作为只读 Realm 打开,MutableSets
则不可变。
您可以使用与MutableSet
结果 相同的谓词 对 进行筛选和排序。与其他 Realm 集合一样,您可以在MutableSet
上注册变更侦听器。
例如,一个 Dog
类模型可能包含一个用于 citiesVisited
的 MutableSet
:
class Dog: Object { var name = "" var currentCity = "" var citiesVisited: MutableSet<String> }
注意
为 @Persisted
MutableSet 属性声明默认值时,以下两种语法类型均有效:
@Persisted var value: MutableSet<String>
@Persisted var value = MutableSet<String>()
但是,第二种语法会导致性能大幅下降。这是因为 MutableSet 是在创建父对象时创建的,而不是按需延迟创建的。
地图/词典
10.8.0 版本新增。
Map是一个关联数组,其中包含具有唯一键的键值对。
就像 Swift 的 字典 一样 ,Map
是根据其键和值类型进行参数化的泛型。与 原生 Swift 集合 不同 Realm Map 是引用类型(类),而不是值类型(结构体)。
您可将 Map 声明为对象的属性:
class Dog: Object { var name = "" var currentCity = "" // Map of city name -> favorite park in that city var favoriteParksByCity: Map<String, String> }
Realm 不允许在映射键中使用 .
或 $
字符。您可以使用百分比编码和解码来存储包含这些不允许的任一字符的映射键。
// Percent encode . or $ characters to use them in map keys let mapKey = "New York.Brooklyn" let encodedMapKey = "New York%2EBrooklyn"
注意
为 @Persisted
Map 属性声明默认值时,以下两种语法类型均有效:
@Persisted var value: Map<String, String>
@Persisted var value = Map<String, String>()
但是,第二种语法会导致性能大幅下降。这是因为 Map 是在创建父对象时创建的,而不是按需延迟创建的。
AnyRealmValue
在版本10.51.0中进行了更改: AnyRealmValue
属性可以保存混合数据的列表或映射。
10.8.0 版本新增。
AnyRealmValue
是一种 Realm 属性类型,它可保存不同数据类型。支持的 AnyRealmValue
数据类型包括:
Int
Float
double
Decimal128
ObjectID
UUID
bool
Date
数据
字符串
名单
Map
对象
AnyRealmValue
不能容纳MutableSet
或嵌入式对象。
这种混合数据类型是可索引的,但不能将其用主键。 由于null
是允许值,因此您不能将AnyRealmValue
声明为可选值。
class Dog: Object { var name = "" var currentCity = "" var companion: AnyRealmValue }
混合集合
在版本10.51.0及更高版本中, AnyRealmValue
数据类型可以包含AnyRealmValue
元素的集合(列表或映射,但不是设立)。 您可以使用混合集合对非结构化或可变数据进行建模。 有关更多信息,请参阅定义非结构化数据。
您最多可以嵌套100级混合集合。
您可以查询混合集合属性并注册变更监听器,就像查询普通集合一样。
您可以查找并更新单个混合集合元素
不能在混合集合中存储集合或嵌入式对象。
要在应用中使用混合集合,请在数据模型中定义AnyRealmValue
类型属性。 然后,您可以像创建任何其他混合数据值一样创建列表或地图集合。
地理空间数据
10.47.0 版本的新增功能。
地理空间数据指定地球表面上的点和几何对象。
如果要持久保存地理空间数据,则必须符合 GeoJSON 规范。
要使用 Swift SDK 持久保存地理空间数据,请创建一个可以在数据模型中使用的 GeoJSON 兼容嵌入式类。
您的自定义嵌入式对象必须包含 GeoJSON 规范所需的两个字段:
String
类型的属性字段,它映射到一个值为"Point"
的type
属性:@Persisted var type: String = "Point"
List<Double>
类型的字段,它映射到一个包含纬度/经度对的coordinates
属性:@Persisted private var coordinates: List<Double>
class CustomGeoPoint: EmbeddedObject { private var type: String = "Point" private var coordinates: List<Double> public var latitude: Double { return coordinates[1] } public var longitude: Double { return coordinates[0] } convenience init(_ latitude: Double, _ longitude: Double) { self.init() // Longitude comes first in the coordinates array of a GeoJson document coordinates.append(objectsIn: [longitude, latitude]) } }
将不支持的类型映射为支持的类型
版本 10.20.0 中的新增功能。
您可以使用类型投影将不支持的类型保留为 Realm 中支持的类型。此举可让您使用 Realm 不支持的 Swift 类型,但可将其存储为 Realm 支持的类型。例如,您可将 URL 存储为 String
,但可像 URL 一样从 Realm 中读取它并在应用程序中使用它。
声明类型投影
要与 Realm 一起使用类型投影:
使用 Realm 的某一自定义类型协议将不支持的数据类型映射为 Realm 支持的类型
在 Realm 对象模型中将这些投影后的类型用作 @Persisted 属性
符合类型投影协议
您可以使用某一 Realm 类型投影协议将不支持的数据类型映射为 Realm 支持的类型。
Swift SDK 提供两种投影协议:
CustomPersistable
FailableCustomPersistable
当转换不可能失败时,请使用 CustomPersistable。
当转换可能失败时,请使用 FailableCustomPersistable。
// Extend a type as a CustomPersistable if if is impossible for // conversion between the mapped type and the persisted type to fail. extension CLLocationCoordinate2D: CustomPersistable { // Define the storage object that is persisted to the database. // The `PersistedType` must be a type that Realm supports. // In this example, the PersistedType is an embedded object. public typealias PersistedType = Location // Construct an instance of the mapped type from the persisted type. // When reading from the database, this converts the persisted type to the mapped type. public init(persistedValue: PersistedType) { self.init(latitude: persistedValue.latitude, longitude: persistedValue.longitude) } // Construct an instance of the persisted type from the mapped type. // When writing to the database, this converts the mapped type to a persistable type. public var persistableValue: PersistedType { Location(value: [self.latitude, self.longitude]) } } // Extend a type as a FailableCustomPersistable if it is possible for // conversion between the mapped type and the persisted type to fail. // This returns nil on read if the underlying column contains nil or // something that can't be converted to the specified type. extension URL: FailableCustomPersistable { // Define the storage object that is persisted to the database. // The `PersistedType` must be a type that Realm supports. public typealias PersistedType = String // Construct an instance of the mapped type from the persisted type. // When reading from the database, this converts the persisted type to the mapped type. // This must be a failable initilizer when the conversion may fail. public init?(persistedValue: String) { self.init(string: persistedValue) } // Construct an instance of the persisted type from the mapped type. // When writing to the database, this converts the mapped type to a persistable type. public var persistableValue: String { self.absoluteString } }
提示
这些协议均以 Swift 的内置 RawRepresentTable 作为蓝本。
支持的 PersistedType
PersistedType
可使用Swift SDK 支持的任何基元类型。 它也可以是嵌入式对象。
PersistedType
不能为可选项或集合。但是,您可将映射后的类型用作对象模型中的可选属性或集合属性。
extension URL: FailableCustomPersistable { // The `PersistedType` cannot be an optional, so this is not a valid // conformance to the FailableCustomPersistable protocol. public typealias PersistedType = String? ... } class Club: Object { var id: ObjectId var name: String // Although the `PersistedType` cannot be optional, you can use the // custom-mapped type as an optional in your object model. var url: URL? }
在模型中使用类型投影
符合某一类型投影协议的类型可与 Swift SDK 版本 10.10.0 中引入的 @Persisted
属性声明语法一起使用。它不适用于 @objc dynamic
语法。
您可将投影类型用于:
顶级类型
类型的可选版本
集合的类型
将 FailableCustomPersistable
用作属性时,请将其定义为可选属性。如果它为可选属性,FailableCustomPersistable
协议则会将无效值映射为 nil
。当它为必需属性时,会将其强制展开。如果存在无法转换为投影类型的值,读取该属性则会引发因未展开而失败的异常。
class Club: Object { var id: ObjectId var name: String // Since we declared the URL as a FailableCustomPersistable, // it must be optional. var url: URL? // Here, the `location` property maps to an embedded object. // We can declare the property as required. // If the underlying field contains nil, this becomes // a default-constructed instance of CLLocationCoordinate // with field values of `0`. var location: CLLocationCoordinate2D } public class Location: EmbeddedObject { var latitude: Double var longitude: Double }
当模型包含投影后的类型时,既可使用持久类型来创建具有值的对象,也可使用投影后的类型为已初始化对象的字段属性分配该对象。
// Initialize objects and assign values let club = Club(value: ["name": "American Kennel Club", "url": "https://akc.org"]) let club2 = Club() club2.name = "Continental Kennel Club" // When assigning the value to a type-projected property, type safety // checks for the mapped type - not the persisted type. club2.url = URL(string: "https://ckcusa.com/")! club2.location = CLLocationCoordinate2D(latitude: 40.7509, longitude: 73.9777)
模式中的类型投影
当您将类型声明为符合类型投影协议时,请指定应在 Realm 中保留的类型。例如,如果您将自定义类型 URL
映射为持久类型的 String
,URL
属性则会在模式中显示为 String
,且对该属性的动态访问也会作用于字符串。
模式不会直接表示映射后的类型。将属性从其持久化类型更改为映射后类型(反之亦然)无需进行迁移。
