Realm的常规使用与线程中的坑
结识 Realm 的催化剂
在我们公司的项目迭代中,由于在之前的聊天这个模块关于用户信息的传值有问题,而之前因为项目经过很多开发者的手,且不提整体的架构有多混乱,就单说缓存这块,就是乱的不行,有的地方用CoreData,有的地方用FMDB, 而且封装的Manager中方法的声明很乱,存取的逻辑也不是很清晰,于是造成了很多我需要取到数据的时候,根本取不到,而当我修改大部分本次版本迭代的需求时,发现这个取不到值问题如果继续沿用之前的逻辑就会非常的麻烦,我要用很多额外方法去跳过之前的坑,只是我决定,在群组聊天的功能中,将用户数据的传值这块逻辑重构.
传值的逻辑还是比较好重构的,我将原有用在这里的 CoreData代码全部都清掉,让整体功能即使不依赖于缓存,依旧可以正确及时的取到需要的数据,只是需要等待服务器的响应,但是如果每次都去走服务端的网络请求,那么体验就太差了,那么缓存便是很重要的一步.
之前说过,现有项目有,有的地方采用了 CoreData,有的用了 FMDB,十分混乱,而我们的用户量还并涉及不到数据迁移的问题.所以我想采用另一套缓存框架来完成我的需求,那么我第一个想到的就是 Realm.
初识 Realm
Realm是一个跨平台的移动数据库引擎,而且,它是专门为移动端数据应用设计的数据持久化方案.不论是 CoreData,还是传统的SQLite,代码都些许冗余.CoreData的笨拙的API和FMDB相对不那么面向对象的操作方式,可能会很多人望而却步或萌生停用的念头,那么这个时候,Realm 出现了.
Realm既不是 CoreData,也不是SQLite,它拥有自己的数据库存取引擎,它可以跨平台使用,也意味着更加快速的存取速度,官方给出的Realm的存取速度比 CoreData快了3倍,但是据说在实际使用中,当数据量很大时候,Realm的速度比 CoreData 快了不值30倍.
说了这么多,你一定对 Realm 也有了些许好感,这篇文章中我并打算介绍关于 Realm 的使用方法,因为文档Relam的官方文档中写的清清楚楚,网上很多大神也做了相关介绍,但是大多数的博客中方法的介绍都是局限在了 Demo 的使用中,真的作用于项目的很少,我在此也算卖弄卖弄我在植入到实际项目时所遇到的小坑或者经验吧.
1.由于 RLMArray 的关系,这句话一定要写,来定义 RLMArray 中的实例,不然会崩溃
2.由于数据模型已经由继承与 NSObject 的 Model, 改为了继承于 RLMObject,所以在使用 KVC 的时候一定要注意.
3.主键
如果你想要更新数据,主键是不错的选择
4.线程
线程问题的坑是我这篇文章所要说的重点.其实 Realm 在关于线程的处理上已经帮我们做了很多事情,我自己并不需要讲过多的精力放在线程上,但是 Realm 本身的线程管理非常严格,所以我们必须遵守Realm 的使用方式,这就使得坑与有点并存
原本我最开始关于缓存的设计思路是,我从服务端拿到了数据,那么我会把数据放入内存中的数组容器中,再存入数据库中,那么下次进入这个页面我就可以先从数据库中拿到数据后放入容器,再通过服务端进行更新,也是说在当前 Controller 中,我只需要通过数组容器进行赋值就可以了.然而 Realm 并不允许你这样,当我将数据存入 Realm 后,我还没有进行取数据的操作,我只是用数据容器,但这时,程序崩溃了, WTF????!!!! 查看一下崩溃信息 Realm accessed from incorrect thread(从错误的线程访问),当时我就委屈了,我存的时候没有崩溃,也没取啊,怎么就崩溃的,后来我又不得已的用蹩脚的英语水平仔细研读了一下原文文档.
蛋疼的开始
同一个 Realm提交过写入就可以在其他线程被各种蹂躏 Realm的实例不是安全线程的不能在其他线程或列队被访问卧槽???看的我是万脸懵逼!!
于是我就交了个 Realm的技术交流群,开始,还有人跟我交流交流使用心德,等把把图贴出来,石沉大海一般,可能是两张截图暴露我的智商和理解能力,大家不想跟差生玩儿...好吧,本着 API 文档就像游戏攻略一样的原则,看不懂的,就带着疑问去玩一玩...那么既然增没事儿,改删查也都没做,那问题会不会出现在了 RLMObject 的调用上,于是在遍历使用之前说的数组容器的地方,打印了当前线程,嗯,果然不是主线程,那既然说同一个 Realm 只要提交了写入就可以在其他线程改变,那我于是试了试 [RLMObject objectWhere:@"查询条件"],发现就完全没有问题的,那原因到底是什么呢?于是我又开始啃文档
你可以有任意数量的线程并行工作在同一个Realm 只是唯一要注意的是你不能在多个线程共享同一个 RealmObject实例这就非常清晰了, Realm 本身在工程中的调用也是个单例类[RLMRealm defaultRealm],所以只要是同一个 Realm, 就可以在任意线程,哪怕是多个线程中,随意使用,不需要锁,只需要将 commitWriteTransaction就可以.但是 RLMObject 的使用的限制就非常严格的,主线程里创建的 RLMObject 就只能在主线程里用,在其他线程中调用的这个它的实例就会抛出异常,有人说,这是 Realm的线程坑,但是我觉得这个是 Realm 对线程做的最好的处理
为此我特意写了一段非常欠打的代码,来验证 Realm 是如何处理并发问题的
#pragma mark 这是第一个子线程 这里面进行更新写入
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current ======= %@",[NSThread currentThread]);
for (int i = 0; i < 35; i ++) {
InformationUpdateModel *model = [[InformationUpdateModel alloc] init];
model.name = [NSString stringWithFormat:@"草鞋%d号",i];
model.age = i;
RLMRealm *relam = [RLMRealm defaultRealm];
[relam transactionWithBlock:^{
[relam addOrUpdateObject:model];
[relam commitWriteTransaction];
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
[InfomationModel allObjects];
});
});
#pragma mark 这是第二个子线程 这里面是查询
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current ======= %@",[NSThread currentThread]);
[InfomationModel allObjects];
});
#pragma mark 这里是第三个子线程 这里是更新写入加查询 回到主线程后继续更新写入加查询
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current ======= %@",[NSThread currentThread]);
for (int i = 60; i < 80; i ++) {
InformationUpdateModel *model = [[InformationUpdateModel alloc] init];
model.name = [NSString stringWithFormat:@"草鞋%d号",i];
model.age = i;
RLMRealm *relam = [RLMRealm defaultRealm];
[relam transactionWithBlock:^{
[relam addOrUpdateObject:model];
[relam commitWriteTransaction];
}];
}
[InfomationModel allObjects];
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 40; i < 50; i ++) {
InformationUpdateModel *model = [[InformationUpdateModel alloc] init];
model.name = [NSString stringWithFormat:@"草鞋%d号",i];
model.age = i;
RLMRealm *relam = [RLMRealm defaultRealm];
[relam transactionWithBlock:^{
[relam addOrUpdateObject:model];
[relam commitWriteTransaction];
}];
}
});
});
我直接开了三个子线程,并在其中用同一个 Realm 各自存取
三个子线程从时间上来看,形成了一个小规模的并发,也是说,会在Realm可能同时即被读,又被写,但是完全没有报出异常,程序流畅运行,数据也没有出现错误.
由于MVCC 架构,不会阻塞读写关于 Realm 就先说到这里,其实 Realm 也有很多不便之处,比如我们的模型必须要继承RLMObject,他的RLMArray也并不是 NSArray类型,所以从代码的独立性角度上来说,就不是特别的完美,如果本身项目中有一套完整严格的数据协议,那么 Realm 可能就不会是一个好的选择,而且本身自带 Model,也被很多架构师的去 Model 化思想想违背,但这并不妨碍它是一套专门用于移动端,速度效率超快, API 非常简洁,线程处理非常棒的持久化解决方案.
后记
第一次写技术文章,诚惶诚恐,其实从代码角度来说,并没有分享什么优质代码,而且废话比较多,可能是因为我个人嘴太碎.这篇文章只是针对自己对于 Realm 的使用做了一些总结,并希望分享出去,这样如果我有理解的不对或者值得讨论的地方,也可以尽快的纠正,当然如果这边文章可以帮到谁,哪怕只有那么一丢丢,我也就心满意足了
其实关于 Realm 的线程处理还有很多更好的方法,如果有机会和时间我会随着业务的深入,再次进行探索,并将心得分享出来共勉.
如果有问题或者不对地方我会及时更正.
/*************************** 2016 - 10 - 18 第一次更新 ****************************/