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
2
3
4
5
6
7
8
9
10
11
12
class Person: Object {
dynamic var firstName: String?
dynamic var lastName: String?
}

let me = Person() me.firstName = "Marin" me.lastName = "Todorov"

// 写入
try realm.write { realm.add(me) }

// 读取
realm.objects(Person.self) .filter("age > 21") .sorted("age")

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
2
3
4
5
6
7
8
9
10
class Car: Object {
@objc dynamic var brand = ""
@objc dynamic var year = 0

convenience init(brand: String, year: Int) {
self.init()
self.brand = brand
self.year = year
}
}

标记为 @objc dynamic 的属性在运行时可通过动态调度(Dynamic Dispatch)机制访问, 从而实现对该属性的:

  • 读: 由 Realm 来提供这个属性的实际数据, 而不是直接从内存读.
  • 写: 由 Realm 将值直接写入到存储(内存或外存).

这样形成如下数据流:

当某个数据模型对象未被加入到 realm 中时, 它就和普通对象一样, 称为 unmanaged(非托管)对象.

Realm 中的 Object 实例都是动态的, 即它们可以自动更新数据视图, 而不用手动刷新. 针对实例中属性的修改可以直接反映到所有该对象的引用上, 即使查询出来的是若干个在内存中的不同对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let myDog = Dog()
myDog.name = "Fido"
myDog.age = 1

try! realm.write {
realm.add(myDog)
}

let myPuppy = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
myPuppy!.age = 2
}

print("age of my dog: \(myDog.age)") // => 2

Realm 数据类型

Realm 中数据类型分为两大类:

  • 原子类型
  • 对象类型

特别地, 在 Swift 存储 enum 时, enum 必须拥有 rawValue.

Schema 间关系

只能对位于同一个 Realm 数据库中的对象建立关系.

一对一关系

一对一关系的简单例子如下所示:

一个更为详细的设计:

一对多关系

通过 Realm 的 List 可以实现一对多关系.

使用 LinkingObjects

Realm 新版本中提供 LinkingObjects 用来手动建立两个对象的关系.

Predicate Cheat-sheet

如下是 predicate 的一个列表:

Property

排序

排序比较简单, 使用如下方式即可进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
let sortedPeople = realm.objects(Person.self)
.filter("firstName BEGINSWITH %@", "J").sorted(byKeyPath: "firstName")
let names = sortedPeople.map { $0.firstName }.joined(separator: ", ")
print("Sorted people: \(names)")

let sortedArticles = realm.objects(Article.self).sorted(byKeyPath: "author.firstName")
print("Sorted articles by author: \n\(sortedArticles.map { "- \ ($0.author!.fullName): \($0.title!)" }.joined(separator: "\n"))")

let sortedPeopleMultiple = realm.objects(Person.self) .sorted(by: [
SortDescriptor(keyPath: "firstName", ascending: true),
SortDescriptor(keyPath: "born", ascending: false)
])
print(sortedPeopleMultiple.map { "\($0.firstName) @ \ ($0.born)" }.joined(separator: ", "))

实践

  1. 实现便捷构造方法来避免外部赋值操作:

    1
    2
    3
    4
    5
    6
    convenience init(firstName: String, born: Date, id: Int) { 
    self.init()
    self.firstName = firstName
    self.born = born
    self.id = id
    }
  2. 设置主键:

    1
    2
    3
    4
    5
    6
    7
    // ...
    @objc dynamic var key = UUID().uuidString

    // 默认实现中返回的是 nil, 意味着默认没有主键
    override static func primaryKey() -> String? {
    return "key"
    }
  3. 避免在移动端使用自增整数作为主键.

  4. 可添加索引加快查询速度:

    1
    2
    3
    override static func indexedProperties() -> [String] {
    return ["firstName", "lastName"]
    }

    其中 firstNamelastName 均为属性名称, 对应数据表的两个列.

    建立索引可有效提高查询或过滤时候的速度, 但会增大数据库文件体积, 且重建索引时会有一定开销. 故应尽量只对频繁查询的数据添加索引.

  5. 忽略数据模型中的某些属性有两种方法:

    • 去掉属性前面的 dynamic @objc 标记

    • 显式声明忽略属性:

      1
      2
      3
      4
      @objc dynamic var temporaryUploadId = 0
      override static func ignoredProperties() -> [String] {
      return ["temporaryUploadId"]
      }
  6. 使用 @objcMembers 标记简化属性字段的书写:

    1
    2
    3
    4
    @objcMembers class Article: Object {
    dynamic var id = 0
    dynamic var title: String?
    }

    使用 @objcMembers 后就不必每一个属性都写 @objc 了.

参考文档

  1. Realm 官方文档.
  2. Realm.Building.Modern.Swift.Apps.with.Realm.Database.2nd 热心网友提供.

Realm 用法快速参考
https://blog.rayy.top/2020/09/19/2020-09-19-realm/
作者
貘鸣
发布于
2020年9月19日
许可协议