【iOS 开发】Realm Swift 数据库使用汇总

由于最近公司需要将项目用 Swift 改写,项目中需要大量使用数据库,之前 OC 使用的是 Core DataCore Data 使用起来确实十分的繁琐,故决定在 Swift 中弃用,改用 Realm 数据库,下面将使用方法记录下来方便以后查看。


Realm 的优点

Realm 不是基于 Core Data,也不是基于 SQLite 封装构建的。它有自己的数据库存储引擎,下面说一下 Realm 的一些优点。

  • 跨平台: 现在很多应用都是要兼顾 iOSAndroid 两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用 Realm 提供的 API,可以使数据持久化层在两个平台上无差异化的转换。代码可以使用 SwiftObjective-C 以及 Java 语言来编写。

  • 简单易用: Core DataSQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用 Realm,则可以极大地减少学习成本,立即学会本地化存储的方法。大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了,这是 中文官方文档地址

  • 可视化: Realm 还提供了一个轻量级的数据库查看工具,在 Mac Appstore 可以下载 Realm Browser 这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。

Realm Browser

Realm Swift 的安装

这是 RealmGitHub 地址 ,其他方法我就不说了,我是用 CocoaPods 方式安装的,所以就只说 CocoaPods 的安装方法了。

  • 安装 CocoaPods 0.39.0 或者更高版本

  • 运行 pod repo update ,以确保 CocoaPods 能够获取到 Realm 的最新版本。

  • 在你的 Podfile 中,添加 use_frameworks!pod 'RealmSwift' 到你的主要和测试目标。

  • 如果你使用的是 Xcode 8,那么将下面代码复制到你的 Podfile 底部,以便在必要的时候更新 Swift 的版本。

1
2
3
4
5
6
7
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '3.0'
end
end
end
  • 在终端运行 pod install

  • 采用 CocoaPods 生成的 .xcworkspace 来运行工程。

  • 在需要使用 Realm Swift 的地方加入 import RealmSwift

Realm Browser 的使用

先说一下 Realm Browser 这个数据库查看工具的使用方法。

1. 模拟器调试

  • 如果是使用模拟器进行调试,首先通过以下代码打印出 Realm 数据库地址。
1
2
let realm = try! Realm()
print(realm.configuration.fileURL!)
  • 然后打开 Finder 按下 command + shift + G 跳转到对应路径下,用 Realm Browser 打开对应的 .realm 文件就可以看到数据了。

.realm 文件

2. 真机调试

  • 如果是真机调试的话,打开 Xcode ,选择菜单 Window 下的 Devices

Devices

  • 选择对应的设备与项目,点击 Download Container

Download Container

  • 导出 xcappdata 文件后,显示包内容,进到 AppData 下的 Documents ,使用 Realm Browser 打开 .realm 文件即可。

Realm Swift 的使用

1. 配置 Realm 数据库

  • 将以下代码写在 AppDelegatedidFinishLaunchingWithOptions 方法中,这个方法主要用于数据模型属性增加或删除时的数据迁移,每次模型属性变化时,将 schemaVersion1 即可,Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构,移除属性的数据将会被删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Realm 数据库配置,用于数据库的迭代更新 */
let schemaVersion: UInt64 = 0
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
/* 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构 */
if (oldSchemaVersion < schemaVersion) {}
})
Realm.Configuration.defaultConfiguration = config
Realm.asyncOpen { (realm, error) in
/* Realm 成功打开,迁移已在后台线程中完成 */
if let _ = realm {
print("Realm 数据库配置成功")
}
/* 处理打开 Realm 时所发生的错误 */
else if let error = error {
print("Realm 数据库配置失败:\(error.localizedDescription)")
}
}
  • 如果属性改变后,想要保留原来已存在的数据来更新新的属性值,在属性变化后将 schemaVersion1 ,并将 config 改为如下,其余不变。
1
2
3
4
5
6
7
8
9
10
11
12
13
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < schemaVersion) {
migration.enumerateObjects(ofType: Dog.className()) { oldObject, newObject in
/* 将 Dog 表中旧的 firstName 和 lastName 属性删除,数据保留合并为 fullName 属性 */
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["fullName"] = "\(firstName) \(lastName)"
}
}
})
  • 如果是只是属性重命名,想保留原来已经存在的数据,重命名以后将 schemaVersion1 ,并将 config 改为如下,其余不变,并且重命名操作应该在调用上面 enumerateObjects(ofType: _:) 之外完成。
1
2
3
4
5
6
7
8
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < schemaVersion) {
/* 将 Dog 表的 name 属性重命名为 fullName */
migration.renameProperty(onType: Dog.className(), from: "name", to: "fullName")
}
})

2. Model 数据模型

Realm 数据模型是基于标准 Swift 类来进行定义的,使用属性来完成模型的具体定义,Realm 模型对象在形式上基本上与其他 Swift 对象相同,你可以给它们添加您自己的方法和协议,和在其他对象中使用类似。

Realm 支持的属性类

Realm 支持这几种属性类型:BoolInt8Int16Int32Int64DoubleFloatStringNSDate 以及 NSData ,下面的表格提供了关于声明模型属性的简易参考。

类型 非可选值形式 可选值形式
Bool dynamic var value = false let value = RealmOptional<Bool>()
Int dynamic var value = 0 let value = RealmOptional<Int>()
Float dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
Double dynamic var value: Double = 0.0 let value = RealmOptional<Double>()
String dynamic var value = "" dynamic var value: String? = nil
Data dynamic var value = NSData() dynamic var value: NSData? = nil
Date dynamic var value = NSDate() dynamic var value: NSDate? = nil
Object 必须是可选值 dynamic var value: Class?
List let value = List<Class>() 必须是非可选值
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") 必须是非可选值

Model 数据模型创建

下面以 DogPerson 为例,通过简单的继承 Object 或者一个已经存在的模型类,你就可以创建一个新的 Realm 数据模型对象。

普通的数据模型
1
2
3
4
5
6
7
8
9
10
11
12
13
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
}
/// 狗狗主人的数据模型
class Person: Object {
dynamic var name: String?
dynamic var birthdate = NSDate()
}
关系绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
dynamic var owner: Person? // 对一关系
}
/// 狗狗主人的数据模型
class Person: Object {
dynamic var name: String?
dynamic var birthdate = NSDate()
let dogs = List<Dog>() // 对多关系
}
反向关系

如果对多关系属性 Person.dogs 链接了一个 Dog 实例,而这个实例的对一关系属性 Dog.owner 又链接到了对应的这个 Person 实例,那么实际上这些链接仍然是互相独立的。

Person 实例的 dogs 属性添加一个新的 Dog 实例,并不会将这个 Dog 实例的 owner 属性自动设置为该 Person

但是由于手动同步双向关系会很容易出错,并且这个操作还非常得复杂、冗余,因此 Realm 提供了 链接对象 (linking objects) 属性来表示这些反向关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
let owner = LinkingObjects(fromType: Person.self, property: "dogs") // 反向关系
}
/// 狗狗主人的数据模型
class Person: Object {
dynamic var name: String?
dynamic var birthdate = NSDate()
let dogs = List<Dog>() // 对多关系
}
索引属性(Indexed Properties)

重写 Object.indexedProperties() 方法可以为数据模型中需要添加索引的属性建立索引。Realm 支持字符串整数布尔值 以及 NSDate 属性作为索引。对属性进行索引可以减少插入操作的性能耗费,加快比较检索的速度(比如说 = 以及 IN 操作符)

1
2
3
4
5
6
7
8
9
10
11
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
override class func indexedProperties() -> [String] {
return ["name"]
}
}
主键(Primary Keys)

重写 Object.primaryKey() 可以设置模型的主键。声明主键之后,对象将允许进行查询,并且更新速度更加高效,而这也会要求每个对象保持唯一性。 一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改。

Realm 可以将 IntString 类型的属性设为主键,但是不支持自增长属性,所以只能自己给主键生成一个唯一的标识,可以使用 UUID().uuidString 方法生成唯一主键。

1
2
3
4
5
6
7
8
9
10
11
12
/// 狗狗的数据模型
class Dog: Object {
dynamic var id = UUID().uuidString
dynamic var name: String?
dynamic var age = 0
override class func primaryKey() -> String? {
return "id"
}
}
忽略属性(Ignored Properties)

重写 Object.ignoredProperties() 可以防止 Realm 存储数据模型的某个属性。Realm 将不会干涉这些属性的常规操作,它们将由成员变量提供支持,并且您能够轻易重写它们的 settergetter

1
2
3
4
5
6
7
8
9
10
11
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
override class func ignoredProperties() -> [String]? {
return ["name"]
}
}

3. 创建数据模型对象

  • 可以用多种方法创建一个新的对象:
1
2
3
4
5
6
7
8
9
10
/* (1) 创建一个狗狗对象,然后设置其属性 */
var myDog = Dog()
myDog.name = "大黄"
myDog.age = 10
/* (2) 通过字典创建狗狗对象 */
let myOtherDog = Dog(value: ["name" : "豆豆", "age": 3])
/* (3) 通过数组创建狗狗对象 */
let myThirdDog = Dog(value: ["豆豆", 5])
  • 即使是数组以及字典的多重嵌套,Realm 也能够轻松完成对象的创建。注意 List 只能够包含 Object 类型,不能包含诸如 String 之类的基础类型。
1
2
3
4
5
/* 这里我们就可以使用已存在的狗狗对象来完成初始化 */
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])
/* 还可以使用多重嵌套 */
let anotherPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])

4. 数据库操作(增删改查)

任何操作都需要获取 Realm 实例,每个线程只需要使用一次即可。

1
2
/* 获取默认的 Realm 实例,每个线程只需要使用一次即可 */
let realm = try! Realm()

增加数据

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 创建一个 Dog 对象 */
let dog = Dog(value: ["name" : "豆豆", "age": 3])
/* 创建一个 Dog 对象数组 */
let dogs = [Dog(value: ["name": "张三", "age": 1]), Dog(value: ["name": "李四", "age": 2]), Dog(value: ["name": "王五", "age": 3])]
/* 通过事务将数据添加到 Realm 中 */
try! realm.write {
realm.add(dog) // 增加单个数据
realm.add(dogs) // 增加多个数据
realm.create(Dog.self, value: ["name" : "豆豆", "age": 3], update: true) // 直接根据 JSON 数据增加
}

删除数据

1
2
3
4
5
6
7
8
9
10
// let dog = ... 存储在 Realm 中的 Dog 对象
// let dogs = ... 存储在 Realm 中的多个 Dog 对象
/* 在事务中删除数据 */
try! realm.write {
realm.delete(dog) // 删除单个数据
realm.delete(dogs) // 删除多个数据
realm.deleteAll() // 从 Realm 中删除所有数据
}

修改数据

  • 内容直接更新: 在事务中直接修改某一条数据。
1
2
3
4
5
6
7
// let dog = ... 存储在 Realm 中的 Dog 对象
/* 在一个事务中修改数据 */
try! realm.write {
dog.name = "张三"
}
  • 通过主键更新: 如果你的数据模型中设置了主键的话,那么你可以使用 Realm().add(_:update:) 来更新数据,如果数据不存在时会自动插入新的数据。
1
2
3
4
5
6
7
8
9
// let dog = ... 存储在 Realm 中的 Dog 对象(有主键)
// let dogs = ... 存储在 Realm 中的多个 Dog 对象(有主键)
/* 在一个事务中修改数据 */
try! realm.write {
realm.add(dog, update: true) // 更新单个数据
realm.add(dogs, update: true) // 更新多个数据
}
  • 键值编码: ObjectResult 以及 List 都遵守 键值编码(Key-Value Coding) 机制。 当你在运行时才能决定哪个属性需要更新的时候,这个方法是最有用的。将 KVC 应用在集合当中是大量更新对象的极佳方式,这样就可以不用经常遍历集合,为每个项目创建一个访问器了。
1
2
3
4
5
6
7
8
// let dogs = ... 存储在 Realm 中的多个 Dog 对象
/* 在一个事务中修改数据 */
try! realm.write {
dogs.first?.setValue("张三", forKeyPath: "name") // 将第一个狗狗名字改为张三
dogs.setValue("张三", forKeyPath: "name") // 将所有狗狗名字都改为张三
}

查询数据

  • 普通查询: 查询数据库中某张表的所有数据。
1
2
/* 从数据库中查询所有狗狗 */
let dogs = realm.objects(Dog.self)
  • 主键查询: 根据 主键 查询某张表的某条数据,模型必须包含主键,否则会崩溃。
1
2
/* 从数据库中查询主键为 1 的狗狗 */
let dog = realm.object(ofType: Dog.self, forPrimaryKey: "1")
  • 条件查询: 根据 断言字符串 或者 NSPredicate 谓词 查询某张表中的符合条件数据。
1
2
3
4
5
6
/* 根据断言字符串从数据库查询 name 为 张三 的狗狗 */
var dogs = realm.objects(Dog.self).filter("name = %@", "张三")
/* 根据 NSPredicate 谓词从数据库查询 age 小于 5 并且 name 以 ‘张’ 开头的狗狗 */
let predicate = NSPredicate(format: "age < 5 AND name BEGINSWITH '张'")
var dogs = realm.objects(Dog.self).filter(predicate)
  • 排序查询: 将查询结果进行排序,可以和条件查询配合使用。
1
2
3
4
5
6
7
8
/* 将查询到的狗狗根据名字升序进行排序 */
let dogs = realm.objects(Dog.self).sorted(byKeyPath: "name")
/* 将查询到的狗狗根据名字降序进行排序 */
let dogs = realm.objects(Dog.self).sorted(byKeyPath: "name", ascending: false)
/* 将查询到的狗狗根据名字和年龄升序进行排序 */
let dogs = realm.objects(Dog.self).sorted(by: ["name", "age"])

想要了解更多可以查看 中文官方文档地址 ,有不足之处之后会补充,OC 版本的话可以看这篇文章:Realm数据库 从入门到“放弃” ,写的非常详细,也参考了不少这篇文章的内容。

将来的你,一定会感激现在拼命的自己,愿自己与读者的开发之路无限美好。

我的传送门: 博客简书微博GitHub

如果觉得我的文章对您有用,请随意打赏吧,您的支持将鼓励我继续创作!
0%