由于最近公司需要将项目用
Swift
改写,项目中需要大量使用数据库,之前OC
使用的是Core Data
,Core Data
使用起来确实十分的繁琐,故决定在Swift
中弃用,改用Realm
数据库,下面将使用方法记录下来方便以后查看。
Realm 的优点
Realm
不是基于 Core Data
,也不是基于 SQLite
封装构建的。它有自己的数据库存储引擎,下面说一下 Realm
的一些优点。
跨平台: 现在很多应用都是要兼顾
iOS
和Android
两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用Realm
提供的API
,可以使数据持久化层在两个平台上无差异化的转换。代码可以使用Swift
、Objective-C
以及Java
语言来编写。简单易用:
Core Data
和SQLite
冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用Realm
,则可以极大地减少学习成本,立即学会本地化存储的方法。大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了,这是 中文官方文档地址。可视化:
Realm
还提供了一个轻量级的数据库查看工具,在Mac Appstore
可以下载Realm Browser
这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。
Realm Swift 的安装
这是 Realm
的 GitHub 地址 ,其他方法我就不说了,我是用 CocoaPods
方式安装的,所以就只说 CocoaPods
的安装方法了。
运行
pod repo update
,以确保CocoaPods
能够获取到Realm
的最新版本。在你的
Podfile
中,添加use_frameworks!
和pod 'RealmSwift'
到你的主要和测试目标。如果你使用的是
Xcode 8
,那么将下面代码复制到你的Podfile
底部,以便在必要的时候更新Swift
的版本。
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
数据库地址。
let realm = try! Realm()
print(realm.configuration.fileURL!)
- 然后打开
Finder
按下command + shift + G
跳转到对应路径下,用Realm Browser
打开对应的.realm
文件就可以看到数据了。
2. 真机调试
- 如果是真机调试的话,打开
Xcode
,选择菜单Window
下的Devices
。
- 选择对应的设备与项目,点击
Download Container
。
- 导出
xcappdata
文件后,显示包内容,进到AppData
下的Documents
,使用Realm Browser
打开.realm
文件即可。
Realm Swift 的使用
1. 配置 Realm 数据库
- 将以下代码写在
AppDelegate
的didFinishLaunchingWithOptions
方法中,这个方法主要用于数据模型属性增加或删除时的数据迁移,每次模型属性变化时,将schemaVersion
加1
即可,Realm
会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构,移除属性的数据将会被删除。
/* 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)")
}
}
- 如果属性改变后,想要保留原来已存在的数据来更新新的属性值,在属性变化后将
schemaVersion
加1
,并将config
改为如下,其余不变。
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)"
}
}
})
- 如果是只是属性重命名,想保留原来已经存在的数据,重命名以后将
schemaVersion
加1
,并将config
改为如下,其余不变,并且重命名操作应该在调用上面enumerateObjects(ofType: _:)
之外完成。
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
支持这几种属性类型:Bool
、Int8
、Int16
、Int32
、Int64
、Double
、Float
、String
、NSDate
以及 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 数据模型创建
下面以 Dog
和 Person
为例,通过简单的继承 Object
或者一个已经存在的模型类,你就可以创建一个新的 Realm
数据模型对象。
普通的数据模型
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
}
/// 狗狗主人的数据模型
class Person: Object {
dynamic var name: String?
dynamic var birthdate = NSDate()
}
关系绑定
/// 狗狗的数据模型
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)
属性来表示这些反向关系。
/// 狗狗的数据模型
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
操作符)
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
override class func indexedProperties() -> [String] {
return ["name"]
}
}
主键(Primary Keys)
重写 Object.primaryKey()
可以设置模型的主键。声明主键之后,对象将允许进行查询,并且更新速度更加高效,而这也会要求每个对象保持唯一性。 一旦带有主键的对象被添加到 Realm
之后,该对象的主键将不可修改。
Realm
可以将 Int
和 String
类型的属性设为主键,但是不支持自增长属性,所以只能自己给主键生成一个唯一的标识,可以使用 UUID().uuidString
方法生成唯一主键。
/// 狗狗的数据模型
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
将不会干涉这些属性的常规操作,它们将由成员变量提供支持,并且您能够轻易重写它们的 setter
和 getter
。
/// 狗狗的数据模型
class Dog: Object {
dynamic var name: String?
dynamic var age = 0
override class func ignoredProperties() -> [String]? {
return ["name"]
}
}
3. 创建数据模型对象
- 可以用多种方法创建一个新的对象:
/* (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
之类的基础类型。
/* 这里我们就可以使用已存在的狗狗对象来完成初始化 */
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])
/* 还可以使用多重嵌套 */
let anotherPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])
4. 数据库操作(增删改查)
任何操作都需要获取 Realm
实例,每个线程只需要使用一次即可。
/* 获取默认的 Realm 实例,每个线程只需要使用一次即可 */
let realm = try! Realm()
增加数据
/* 创建一个 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 数据增加
}
删除数据
// let dog = ... 存储在 Realm 中的 Dog 对象
// let dogs = ... 存储在 Realm 中的多个 Dog 对象
/* 在事务中删除数据 */
try! realm.write {
realm.delete(dog) // 删除单个数据
realm.delete(dogs) // 删除多个数据
realm.deleteAll() // 从 Realm 中删除所有数据
}
修改数据
- 内容直接更新: 在事务中直接修改某一条数据。
// let dog = ... 存储在 Realm 中的 Dog 对象
/* 在一个事务中修改数据 */
try! realm.write {
dog.name = "张三"
}
- ** 通过主键更新: ** 如果你的数据模型中设置了主键的话,那么你可以使用
Realm().add(_:update:)
来更新数据,如果数据不存在时会自动插入新的数据。
// let dog = ... 存储在 Realm 中的 Dog 对象(有主键)
// let dogs = ... 存储在 Realm 中的多个 Dog 对象(有主键)
/* 在一个事务中修改数据 */
try! realm.write {
realm.add(dog, update: true) // 更新单个数据
realm.add(dogs, update: true) // 更新多个数据
}
-
键值编码:
Object
、Result
以及List
都遵守 键值编码(Key-Value Coding) 机制。 当你在运行时才能决定哪个属性需要更新的时候,这个方法是最有用的。将KVC
应用在集合当中是大量更新对象的极佳方式,这样就可以不用经常遍历集合,为每个项目创建一个访问器了。
// let dogs = ... 存储在 Realm 中的多个 Dog 对象
/* 在一个事务中修改数据 */
try! realm.write {
dogs.first?.setValue("张三", forKeyPath: "name") // 将第一个狗狗名字改为张三
dogs.setValue("张三", forKeyPath: "name") // 将所有狗狗名字都改为张三
}
查询数据
- 普通查询: 查询数据库中某张表的所有数据。
/* 从数据库中查询所有狗狗 */
let dogs = realm.objects(Dog.self)
-
主键查询: 根据
主键
查询某张表的某条数据,模型必须包含主键,否则会崩溃。
/* 从数据库中查询主键为 1 的狗狗 */
let dog = realm.object(ofType: Dog.self, forPrimaryKey: "1")
-
条件查询: 根据
断言字符串
或者NSPredicate 谓词
查询某张表中的符合条件数据。
/* 根据断言字符串从数据库查询 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)
- 排序查询: 将查询结果进行排序,可以和条件查询配合使用。
/* 将查询到的狗狗根据名字升序进行排序 */
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数据库 从入门到“放弃” ,写的非常详细,也参考了不少这篇文章的内容。
将来的你,一定会感激现在拼命的自己,愿自己与读者的开发之路无限美好。