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的技术交流群,开始,还有人跟我交流交流使用心德,等把把图贴出来,石沉大海一般,可能是两张截图暴露我的智商和理解能力,大家不想跟差生玩儿...好吧,本着 API 文档就像游戏攻略一样的原则,看不懂的,就带着疑问去玩一玩...那么既然增没事儿,改删查也都没做,那问题会不会出现在了 RLMObject 的调用上,于是在遍历使用之前说的数组容器的地方,打印了当前线程,嗯,果然不是主线程,那既然说同一个 Realm 只要提交了写入就可以在其他线程改变,那我于是试了试 [RLMObject objectWhere:@"查询条件"],发现就完全没有问题的,那原因到底是什么呢?于是我又开始啃文档
这就非常清晰了, 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可能同时即被读,又被写,但是完全没有报出异常,程序流畅运行,数据也没有出现错误.
关于 Realm 就先说到这里,其实 Realm 也有很多不便之处,比如我们的模型必须要继承RLMObject,他的RLMArray也并不是 NSArray类型,所以从代码的独立性角度上来说,就不是特别的完美,如果本身项目中有一套完整严格的数据协议,那么 Realm 可能就不会是一个好的选择,而且本身自带 Model,也被很多架构师的去 Model 化思想想违背,但这并不妨碍它是一套专门用于移动端,速度效率超快, API 非常简洁,线程处理非常棒的持久化解决方案.
后记
第一次写技术文章,诚惶诚恐,其实从代码角度来说,并没有分享什么优质代码,而且废话比较多,可能是因为我个人嘴太碎.这篇文章只是针对自己对于 Realm 的使用做了一些总结,并希望分享出去,这样如果我有理解的不对或者值得讨论的地方,也可以尽快的纠正,当然如果这边文章可以帮到谁,哪怕只有那么一丢丢,我也就心满意足了
其实关于 Realm 的线程处理还有很多更好的方法,如果有机会和时间我会随着业务的深入,再次进行探索,并将心得分享出来共勉.
如果有问题或者不对地方我会及时更正.
作者:Wilson魏
链接:http://www.jianshu.com/p/dd5c7f926218
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Realm的常规使用与线程中的坑的更多相关文章
- 在Java 线程中返回值的用法
http://icgemu.iteye.com/blog/467848 在Java 线程中返回值的用法 博客分类: Java Javathread 有时在执行线程中需要在线程中返回一个值:常规中我们 ...
- Python并发编程之谈谈线程中的“锁机制”(三)
大家好,并发编程 进入第三篇. 今天我们来讲讲,线程里的锁机制. 本文目录 何为Lock( 锁 )?如何使用Lock( 锁 )?为何要使用锁?可重入锁(RLock)防止死锁的加锁机制饱受争议的GIL( ...
- 解决 spring boot 线程中使用@Autowired注入Bean的方法,报java.lang.NullPointerException异常
问题描述 在开发中,因某些业务逻辑执行时间太长,我们常使用线程来实现.常规服务实现类中,使用 @Autowired 来注入Bean,来调用其中的方法.但如果在线程类中使用@Autowired注入的Be ...
- 在非UI线程中自制Dispatcher
在C#中,Task.Run当然是一个很好的启动新并行任务的机制,但是因为使用这个方法时,每次新的任务都会在一个新的线程中(其实就是线程池中的线程)运行 这样会造成某些情形下现场调度的相对困难,即使我隔 ...
- 线程中调用python win32com
在python的线程中,调用win32com.client.Dispatch 调用windows下基于COM组件的应用接口, 需要在调用win32com.client.Dispatch前,调用pyth ...
- 为什么说invalidate()不能直接在线程中调用
1.为什么说invalidate()不能直接在线程中调用?2.它是怎么违背单线程的?3.Android ui为什么说不是线程安全的?4.android ui操作为什么一定要在UI线程中执行? 1. ...
- JAVA 线程中的异常捕获
在java多线程程序中,所有线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),也就是说各个线程需要自己把自己的checked e ...
- 【转载】Delphi7从子线程中发送消息到主线程触发事件执行
在对数据库的操作时,有时要用一个子线程来进行后台的数据操作.比如说数据备份,转档什么的.在主窗口还能同是进行其它操作.而有时后台每处理一个数据文件,要向主窗口发送消息,让主窗口实时显示处理进度在窗口上 ...
- Android的post()方法究竟运行在哪个线程中
Android中我们常用的post()方法大致有两种情况: 1.如果post方法是handler的,则Runnable执行在handler依附线程中,可能是主线程,也可能是其他线程 2.如果post方 ...
随机推荐
- 使用BusyBox制作根文件系统
1.BusyBox简介 BusyBox 是很多标准 Linux 工具的一个单个可执行实现.BusyBox 包含了一些简单的工具,例如 cat 和 echo,还包含了一些更大.更复杂的工具,例如 gre ...
- Numpy学习1
NumPy学习(1) 参考资料: http://www.cnblogs.com/zhanghaohong/p/4854858.html http://linusp.github.io/2016/02/ ...
- python find命令、startwith命令
python的字符串有很多好用的操作,比如find,startswith命令. 这几个命令在处理配置文件的时候很有用,比如用startswith判断是否是注释行. 注意:几个函数的返回值是不同滴. 函 ...
- consul 小結
Consul Config 使用Git做版本控制的实现 https://segmentfault.com/a/1190000013807641 服务发现 - consul 的介绍.部署和使用 http ...
- Spring Boot与Spring的区别
转自:https://blog.csdn.net/sinat_36246371/article/details/72902406 Spring Boot是最近这几年才火起来的,那么它到底与Spring ...
- keepalived与nginx安装
目的: 当用户请求访问时,会通过nginx来访问web服务应用,因此我们必须要保证nginx的高可用,要保证nginx的高可用,我们需要通过keepalived来监控nginx,并对外提供1个虚拟的v ...
- Java线程池可用的队列
Java线程池ThreadPoolExecutor的构造器: public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long ...
- 微信公众平台开发(一) 配置接口与验证URL
一.简介 微信公众平台是腾讯公司在微信的基础上新增的功能模块,通过这一平台,个人和企业都可以打造一个微信的公众号,并实现和特定群体的文字.图片.语音的全方位沟通.互动. 二.通讯机制 三.注册微信平台 ...
- [javascript]Dom操作笔记
1.为一个节点同时设置多个属性 $("div[aria-describedby='F53_batch_history']").attr({"display":& ...
- Les13 性能管理
目标 使用Oracle Enterprise Manager监视性能 使用自动内存管理(AMM) 使用内存指导调整内存缓冲区的大小 查看与性能相关的动态视图 排除无效和不可用对象产生的故障 性能监视 ...