Realm 用法快速参考
本文最后更新于 2021年4月4日 晚上
Realm 提供了如下两个主要产品:
- Realm ORM 数据库: 开源的 ORM 数据库. (写这篇文章时 iOS 端版本: 4.3.2)
- Realm 平台: 商用的数据同步(syncing server solution)解决方案, 可以打通多个平台(移动端, 桌面端).
本文主要介绍 Realm 数据库(简称 Realm).
1 Realm 数据库简介
Realm 数据库主要用于客户端数据持久化(不过近期也发布了服务器版本, 但主要应用领域还是在客户端), 和老牌的 SQLite 相比, 它们各有各的优势:
- SQLite: 速度快, 且遵循 SQL 标准. 使用 C 实现并且可在多个平台移植. 但正是这样, 造成开发速度较慢(需要写纯 SQL, 虽然可以通过一些三方库部分加以解决).
- Realm: 现代化的数据库解决方案, 擅长快速读写数据. 但需要使用者对其 API 有一定程度的掌握.
就开发角度看, 无疑 Realm 更加符合现代 APP 的开发需求. 下面是一段最简的使用代码:
1 |
|
Realm 的基石正是对象, 满足现代 APP 开发的一个重要需求: 类型安全. 传统的数据库通常是如下图这样的流程, 在开发中不得不多次对数据进行转换:
而使用 Realm 时, 只需要如下的流程:
另外, Realm 可以自动检测模型类的变化, 从而对应修改数据库 schema.
Realm 的核心是 Realm Core, 采用 C++ 实现, 并根据不同平台使用不同语言提供了多个 SDK, 且本地 API 的行为一致:
特别说明的是, Swift 版的 Realm 是 OC 版的封装.
Realm Object 及其运作
数据模型类必须继承自 Object
, schema 对应属性需要使用 dynamic var
修饰. 当程序运行后, Realm 会自动查找所有的 Object
子类, 将这些类作为数据持久化时候的 Data Schema
.
数据模型的定义如下所示:
1 |
|
标记为 @objc dynamic
的属性在运行时可通过动态调度(Dynamic Dispatch)机制访问, 从而实现对该属性的:
- 读: 由 Realm 来提供这个属性的实际数据, 而不是直接从内存读.
- 写: 由 Realm 将值直接写入到存储(内存或外存).
这样形成如下数据流:
当某个数据模型对象未被加入到 realm 中时, 它就和普通对象一样, 称为 unmanaged(非托管)对象.
Realm 中的 Object
实例都是动态的, 即它们可以自动更新数据视图, 而不用手动刷新. 针对实例中属性的修改可以直接反映到所有该对象的引用上, 即使查询出来的是若干个在内存中的不同对象:
1 |
|
Realm 数据类型
Realm 中数据类型分为两大类:
- 原子类型
- 对象类型
特别地, 在 Swift 存储 enum 时, enum 必须拥有 rawValue.
Schema 间关系
只能对位于同一个 Realm 数据库中的对象建立关系.
一对一关系
一对一关系的简单例子如下所示:
一个更为详细的设计:
一对多关系
通过 Realm 的 List
可以实现一对多关系.
使用 LinkingObjects
Realm 新版本中提供 LinkingObjects
用来手动建立两个对象的关系.
Predicate Cheat-sheet
如下是 predicate 的一个列表:
Property
排序
排序比较简单, 使用如下方式即可进行:
1 |
|
实践
实现便捷构造方法来避免外部赋值操作:
1
2
3
4
5
6convenience init(firstName: String, born: Date, id: Int) {
self.init()
self.firstName = firstName
self.born = born
self.id = id
}设置主键:
1
2
3
4
5
6
7// ...
@objc dynamic var key = UUID().uuidString
// 默认实现中返回的是 nil, 意味着默认没有主键
override static func primaryKey() -> String? {
return "key"
}避免在移动端使用自增整数作为主键.
可添加索引加快查询速度:
1
2
3override static func indexedProperties() -> [String] {
return ["firstName", "lastName"]
}其中
firstName
和lastName
均为属性名称, 对应数据表的两个列.建立索引可有效提高查询或过滤时候的速度, 但会增大数据库文件体积, 且重建索引时会有一定开销. 故应尽量只对频繁查询的数据添加索引.
忽略数据模型中的某些属性有两种方法:
去掉属性前面的
dynamic @objc
标记显式声明忽略属性:
1
2
3
4@objc dynamic var temporaryUploadId = 0
override static func ignoredProperties() -> [String] {
return ["temporaryUploadId"]
}
使用
@objcMembers
标记简化属性字段的书写:1
2
3
4@objcMembers class Article: Object {
dynamic var id = 0
dynamic var title: String?
}使用
@objcMembers
后就不必每一个属性都写@objc
了.