Realm是一个移动端数据库容器.本文根据Realm的官方文档自行翻译, 如有不准望指正.
Realm是一个跨平台的移动数据库引擎,目前支持iOS, Android, Swift, Java, Reat等多种语言.
Realm并不是对SQLite或者CoreData的简单封装, 是由核心数据引擎C++打造,是拥有独立的数据库存储引擎,可以方便、高效的完成数据库的各种操作.
打开本地Realm
开启Realm的方法就是初始化一个Realm对象
1 |
|
配置本地Realm
再打开数据库之前创建一个 Realm.Configuration
对象, 然后赋值给对应的属性, 常见配置如下:
- 数据库路径
- 数据库的迁移函数
- 数据压缩函数(用来优化数据库的磁盘空间)
配置可以通过如下两种方式实现:
1 |
|
例如: 根据指定用户名访问不同的数据库
1 |
|
你可以配置多个参数, 来控制版本,url等信息:
1 |
|
默认Realm
我们可以使用 Realm()
可以生成一个名为 default.realm
, 并存放在Documents
中的数据库.
内存数据库(in-memory Realm)
通过设置 inMemoryIdentifier
而不是 fileURL
到 Realm.Configuration
我们就能创建一个完全运行在内存中的数据库.
1 |
|
内存数据库在暂存目录中写入了多个文件. 当in-memory Realm对象被释放时所有存储在其中的数据都会被删除. 所以建议在app运行时创建一个被强引用的in-memory Realm对象.
错误处理
和其他I/O操作一样, 创建一个Realm对象有时候也会失败. 实际上这种情况只会出现在第一次创建的时候.因为随后在相同线程中获取Realm将会使用第一次创建的缓存,并且获取缓存不会失败.
处理第一次获取Realm对象时的失败情况我们使用的是Swift内置的错误处理机制:
1 |
|
创建Realm产生的附件
在 xxx.realm文件产生的同时, Realm也会生成或者维护额外的文件和目录来完成它的内部操作. 例如:
.realm.lock
- 资源锁文件..realm.management
- 内部处理的锁目录..realm.note
- 通知名录文件.
以上文件对 .realm没有任何影响, 删掉其父级数据库文件不会对他们造成任何影响.
捆绑 Reaml
app初始化的时候可能需要很多数据, 也就是说在我们第一次启动app时,我们可以使用Realm预埋数据.方法如下;
- 给数据库添加数据, 一旦创建好数据模型就不能轻易改变.Reaml是跨平台的, 你可以使用macOS或者iOS模拟器运行JSONImport
- 在生成Reaml文件的的代码地方, 你应该做一个压缩副本(参考:
Realm().writeCopyToPath(_:encryptionKey:)
). 这样可以减小Realm文件的大小,使你的app包变小一些. - 将第2步中的压缩副本拖入xcode项目中
- 在xcode中找到
build phases
,并将Realm文件添加到Copy Bundle Resources
中. - 现在资源目录中的Reamlm文件已经能被app访问. 你能通过
NSBundle.main.pathForResource(_:ofType:)
找到. - 如果资源目录中的Realm文件包含一些固定的数据,你可以设置
Realm.Configuration
为只读readOnly = true
来打开 Realm. 当然,如果这些初始化的数据需要修改, 你可以将bundle中的realm文件通过NSFileManager.default.copyItemAtPath(_:toPath:)
复制到程序的Documents目录中,然后再做修改.
要了解更多使用bundled Realm的使用方法请访问migration sample app
类分组(Class subsets)
有些情况你想限定哪些类能被存到你指定的Realm. 例如, 协同开发过程中, 都在使用Realm, 你就可以限定对象类型.
1 |
|
压缩数据(Compacting Realms)
Realm工作工程中Realm文件总是会比存储在其内部所有文件要大. 为了避免在调用方法时消耗过多资源, Realm不会在运行时进行压缩. 相反,他们的大小会不端增加.当然也会出现没有用处的Realm占用空间的情况.为了处理这个问题,你可以在配置文件中规定在什么情况下要压缩Realm文件. 例如;
1 |
|
-
压缩操作过程是现将所有文件读出来, 写到另一个路径中, 然后替换原来的文件. 如果数据量很大, 这将是一个很昂贵的操作.
-
你需要在良好的性能和占用空间大小这两个因素中找到一个平衡点.即在保持良好性能的同时尽量控制数据文件占用内存的大小.
-
最后要注意一点: 当Realm正在被访问时不会进行压缩操作, 即使符合上面block中的条年也不会, 因为当Realm被访问时如果进行压缩操作不能保证此次访问的安全
删除Realm文件 (Deleting Realm files)
如果你想清缓存或者重置数据, 可能需要将Realm从磁盘中删除.除非显示的声明,Realm会禁止将数据拷贝到内存中,所有被Realm管理的资源在删除之前必须先被释放. 包括从Realm中读取的或者被添加进去的所有列表, 对象以及Realm自己.
实际上删除操作应该在app启动时,打开Realm之前或者在显示声明的自动释放池中打开的Realm释放之后.
最后再删除Realm的同时,你也要删掉Realm的所有附件文件.
1 |
|
模型(Models)
Realm数据的模型和常规Swift的类的定义方式一致. 但是必须继承自 Object
或者Realm模型类的子类.你可以定义方法, 遵循协议等等.但是一定要注意: 你只能在你创建的线程中使用它, 不能跨线程使用
定义数组
1 |
|
Realm在app启动时就会解析模型,所以所有模型必须是有效的, 即使他们不会被用到. 而且所有非可选类型必须有默认值.
支持的类型(Supported property types)
-
Realm支持如下类型:
Bool, Int, Int8, Int16, Int32, Int64, Double, Float, String, Date, and Data.
-
CGFloat
不可使用,因为他OC独有的, 不能跨平台. -
String
,Date
andData
支持可选类型.Object
必须是可选类型. 存储基本数据类型的可选类型使用RealmOptional
.
不可选属性(Required properties)
String
, Date
, Data
可以使用可选或者不可选.
1 |
|
RealmOptional
支持 Int, Float, Double, Bool
, 以及Int的不同类型 (Int8, Int16, Int32, Int64
).
主键(Primary keys)
可以通过重写Object.primaryKey()
方法设置模型的主键.但是一旦主键被设置就不能更改.
1 |
|
索引属性(Indexing properties)
索引一个属性可以通过重写 Object.indexedProperties()
. 和主键一样, 索引会稍微减慢写数据的速度(同时也会使Realm文件变大一些), 但是可以提高查询的速度. 所以最好在需要做查询优化的表中使用索引. Realm支持索引的类型有 string, integer,boolean, date
1 |
|
忽略属性(Ignoring properties)
如果你不想保存模型中的摸个字段,重写 Object.ignoredProperties()
即可.Realm不会干涉这些被忽略属性的常规操作, 你可以重写他们的get和set方法, 以及KVO等,但是即使他们继承自Object
但是不能使用Realm特有的函数 .并且,被声明为只读属性的会自动被Realm忽略.
1 |
|
属性特点(Property attributes)
所有Realm数据模型属性必须以@objc dynamic var开头,除了三种列外情况: LinkingObjects
, List
以及 RealmOptional
.这三中类型不能被定义为dynamic,因为泛型在OC运行时不能被表示.这三种类型必须用let定义.
属性对应清单
Type | Non-optional | Optional |
---|---|---|
Bool | @objc dynamic var value = false | let value = RealmOptional |
Int | @objc dynamic var value = 0 | let value = RealmOptional |
Float | @objc dynamic var value: Float = 0.0 | let value = RealmOptional |
Double | @objc dynamic var value: Double = 0.0 | let value = RealmOptional |
String | @objc dynamic var value = “” | @objc dynamic var value: String? = nil |
Data | @objc dynamic var value = Data() | @objc dynamic var value: Data? = nil |
Date | @objc dynamic var value = Date() | @objc dynamic var value: Date? = nil |
Object | n/a: must be optional | @objc dynamic var value: Class? |
List | let value = List |
n/a: must be non-optional |
LinkingObjects | let value = LinkingObjects(fromType: Class.self, property: “property”) | n/a: must be non-optional |
使用Realm对象
自动更新对象(Auto-updating objects)
对象实例是实时的,自动更新的.有不用手动去刷新数据对象.修改一个对象的属性,可以在其被引用的其他地方立刻反应出来.
1 |
|
这样不仅能保持Realm的高效, 也是代码更加简单明了.如果你的UI依赖一个Realm对象, 你完全不用担心触发UI重绘时数据的刷新和重复获取. 你可以直接订阅一个Realm 通知去监听Realm数据的变化,然后更新UI.
模型的继承(Model inheritance)
Realm允许继承,但是一些运行时特性却不被支持. 支持特性如下:
- 类方法, 对象方法,以及父类属性可以被子类继承.
- 方法和函数的参数为父类的可以在子类运行.
如下特性不支持:
- 多态类型转化(子类转化为子类, 子类转化为父类)
- 多线程查询
-
多种对象容器
// Base Model class Animal: Object { @objc dynamic var age = 0 }
// Models composed with Animal class Duck: Object { @objc dynamic var animal: Animal? = nil @objc dynamic var name = “” } class Frog: Object { @objc dynamic var animal: Animal? = nil @objc dynamic var dateProp = Date() }
// Usage let duck = Duck(value: [ “animal”: [ “age”: 3 ], “name”: “Gustav” ])
集合
Realm有多重能表示集合的类型, 如下:
- Results, 专门用来容纳查询结果的集合
- List
- LinkingObject标示类关系的模型列表
- RealmCollection,这个是所有集合对象都要遵循的协议
- AnyRealmCollection,能代表
Results
,List
和LinkingObjects
.
使用RealmCollection协议泛型的用法如下:
1 |
|
由于Swift类型限制, 使用AnyRealmCollection做类型约束:
1 |
|
数据库之间的对象复制(Copying objects between Realms)
Real之间的对象复制很简单,使用Realm().create(_:value:update:). 一步到位.但是要注意Realm对象只能在第一次创建它的线程操作,所以复制也只有在相同线程才会起作用.另外复制的对象也不能包含涉及集成关系.
关系(Relationships)
建立两个Realm之间的关系在Realm中不论是运行速度还是占用内存都做了非常好的优化.
多对一
1 |
|
多对多
1 |
|
关系反转(Inverse relationships)
按照上面的例子, 如果Person.dogs连接到一个Dog实例, 你能根据person,找到dog, 但是想通过dog找到Person是不可能的. 当然你可以设置一对一的关系将dog.owner连接到Person,但是这些关系每个都是相对独立的. 添加一条dog到person.owner不会设置dog.owner到对应Person.为了解决这个问题Realm提供了如下方法:
1 |
|
通过连接对象属性你可以获取连接到是定属性的所有对象.