当前位置:网站首页>移动端 realm数据库使用及解耦,跨线程安全使用 OC realm
移动端 realm数据库使用及解耦,跨线程安全使用 OC realm
2022-06-22 05:23:00 【zhaocarbon】

最近在搞IM,因为消息量多,实时读写及并发相当的多,原项目封装使用了FMDB,但这个东西对比于并发多线程读写实在不敢恭维,动不动出现锁导致卡顿,体验极期扯蛋。虽然实现队列读写,但是还是管不住这东西的缺陷。。于是乎寻找替换方案。
首先考虑了WCDB,即微信团队发布的开源数据库,但是使用起来相当的别扭,所有属性字段得一个一个的去使用他的宏去声明,工作太繁锁,相信软件及开源的初衷一定是减少开发者的代码耦合及胶水坨坨。
再次考虑都查了Realm这个开源库,库本身为500多M,使用后各种崩溃,100%都是跨线程使用,虽然这个东西,官方说明是线程并发安全的,经过测试也确实是安全的,但是当你使用不当,则全面崩溃异常。原因就是我们的原则数据处理在子线程,刷新UI在主线程。我们在子线程配合加载器(类似于toast)加载数据 ,在主线程使用刷新页面,这个时候,这个东东就不安全了,各种内部线程检查崩溃,对于已有的项目,去适配他的理念及其思想,明显是不明智的,你也不知道哪里要改,哪里要去检查,这样完完全全不是开发的思维,只会白白浪费时间与精力。
故而在寻思realm的安全使用方案,不得不进行解耦,剥离他的内部线程管理方案。即我只用你保存、读取数据 ,页面中的数据model完全是独立于Realm管理的,这个使用方案必定会影响realm的使用性能,但是为了快速在项目中集成使用,不得不这么做。
先来看看这个东西怎么使用:
Realm官网介绍了很多,奈何时间有限,无法去通读,只能从网上查询总结一二,Realm最吸引人的优点就三点:
跨平台:现在很多应用都是要兼顾iOS和Android两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用Realm提供的API,可以使数据持久化层在两个平台上无差异化的转换。
简单易用:Core Data 和 SQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用 Realm,则可以极大地减少学习成本,立即学会本地化存储的方法。毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了。
可视化:Realm 还提供了一个轻量级的数据库查看工具,在Mac Appstore 可以下载“Realm Browser”这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。毕竟,很多时候,开发者使用数据库的理由是因为要提供一些所谓的“知识库”。

“Realm Browser”这个工具调试起Realm数据库实在太好用了,强烈推荐。
如果使用模拟器进行调试,可以通过
[RLMRealmConfiguration defaultConfiguration].fileURL
打印出Realm 数据库地址,然后在Finder中⌘⇧G跳转到对应路径下,用Realm Browser打开对应的.realm文件就可以看到数据啦.
如果是使用真机调试的话“Xcode->Window->Devices(⌘⇧2)”,然后找到对应的设备与项目,点击Download Container,导出xcappdata文件后,显示包内容,进到AppData->Documents,使用Realm Browser打开.realm文件即可.
安全使用,这里我就直接贴这个兄台的分享经验内容:
大家去看集成使用即可。本文的重点是在解耦,即在我们项目中安全的多线程中使用realm的读写。
贴上以下代码:IMSessionDaoScheme.h文件内容
//
// IMSessionDaoScheme.h
// IMCode
//
// Created by carbonzhao on 2022/6/10.
//
//
#if __has_include(<Realm/Realm.h>)
#import <Realm/Realm.h>
@interface RLMStringModel : RLMObject
@property (copy,nonatomic) NSString *strValue;
@end
RLM_ARRAY_TYPE(RLMStringModel)
/**根据REALM原则 ,数据查询在什么线程则在什么线程使用,跨线程使用查询出来的RLMObject及其他对象,均会崩溃。IMSessionDaoScheme实现解耦,所有查询出来的RLMObject进行二次对象替换,保证数据为非REALM托管的对象。*/
@interface IMSessionDaoScheme : RLMObject
//RLMObject实现已经阉割了OC的RUNTIME方法,无法通过class_copyPropertyList来获取其属性及值
+ (NSArray <NSString *>*)allKeys;
- (NSMutableDictionary *)keysValues;
//增、改
- (void)save2Dao;
+ (void)save2Dao:(NSArray <IMSessionDaoScheme *>*)resArr;
//修改某个属性值
- (void)updateOncePropertyValue:(void (^)(id thisModel))ablock;
//删
- (void)deleteFromDao;
+ (void)deleteFromDao:(NSArray <IMSessionDaoScheme *>*)resArr;
+ (void)deleteAll;
+ (NSMutableArray <IMSessionDaoScheme *>*)queryAll;
+ (NSMutableArray <IMSessionDaoScheme *>*)queryFromDaoWithStatement:(NSDictionary *)stej;
+ (NSMutableArray <IMSessionDaoScheme *>*)queryFromDaoWithWhere:(NSString *)sttext;
/**查 ifAnd=yes,sql statement will be liked key=1 AND key1=2,other else key=1 OR key1=2....,
sortedKey can be key desc,key1 asc,use ',' seperator the statement
*/
+ (NSMutableArray <IMSessionDaoScheme *>*)queryFromDaoWithStatement:(NSDictionary *)stej ifAnd:(BOOL)ifAnd sortedKey:(NSString *)sortedKey;
@end
// This protocol enables typed collections. i.e.:
// RLMArray<IMSessionDaoScheme *><IMSessionDaoScheme>
RLM_ARRAY_TYPE(IMSessionDaoScheme)
#endif
IMSessionDaoScheme.m文件内容
//
// IMSessionDaoScheme.m
// IMCode
//
// Created by carbonzhao on 2022/6/10.
//
//
#import "IMSessionDaoScheme.h"
#import "JsonKit.h"
#import "NSExtentionSloter.h"
#import "IMDataConfigTools.h"
#if __has_include(<Realm/Realm.h>)
@implementation RLMStringModel
@end
@interface IMSessionDaoScheme ()
@property (nonatomic,strong) NSString *usersTable;
@property BOOL spcyInMainThread;
@end
@implementation IMSessionDaoScheme
#pragma mark - daoLayer
+ (NSArray *)ignoredProperties
{
return @[@"spcyInMainThread"];
}
+ (NSArray *)allKeys
{
Class csName = [self class];
IMSessionDaoScheme *aj = [[csName alloc] init];
NSArray <RLMProperty *>*allKeys = [aj objectSchema].properties;
NSMutableArray *list = [NSMutableArray arrayWithCapacity:0];
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[list addObject:obj.name];
}];
return list;
}
- (NSMutableDictionary *)keysValues
{
__weak typeof(self) this = self;
NSMutableDictionary *jic = [NSMutableDictionary dictionaryWithCapacity:0];
NSArray <RLMProperty *>*allKeys = [self objectSchema].properties;
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *key = [obj name];
NSString *vl = [this valueForKey:key];
if (obj.type == RLMPropertyTypeArray)
{
NSMutableArray *list = [NSMutableArray arrayWithCapacity:0];
RLMArray *arr = (RLMArray *)vl;
for (NSInteger idx=0; idx<arr.count; idx++)
{
RLMObject *t = [arr objectAtIndex:idx];
if ([obj.objectClassName isEqualToString:@"RLMStringModel"])
{
NSString *value = [(RLMStringModel *)t strValue];
[list addObject:value];
}
else
{
//
}
}
[jic setValue:list forKey:key];
}
else
{
if (![key isEqualToString:@"usersTable"] && ![key isEqualToString:@"spcyInMainThread"])
{
[jic setValue:vl forKey:key];
}
}
}];
return jic;
}
+ (NSMutableDictionary *)daoSchemePropertiesType
{
Class csName = [self class];
IMSessionDaoScheme *ab = [[csName alloc] init];
NSMutableDictionary *jic = [NSMutableDictionary dictionaryWithCapacity:0];
NSArray <RLMProperty *>*allKeys = [ab objectSchema].properties;
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *key = [obj name];
RLMPropertyType type = [obj type];
[jic setValue:intToStr(type) forKey:key];
}];
return jic;
}
//增、改
- (void)save2Dao
{
NSString *accid = [IMDataConfigTools instance].imAccountBlock();
__weak typeof(self) ptr = self;
// NSString *pkey = [[self class] primaryKey];
// NSString *stext = [NSString stringWithFormat:@"usersTable='%@' AND %@='%@'",accid,pkey,[self valueForKey:pkey]];
NSArray <RLMProperty *>*allKeys = [self objectSchema].properties;
Class csName = NSClassFromString([[self objectSchema] className]);
IMSessionDaoScheme *ab = [[csName alloc] init];
NSDictionary *typeOj = [csName daoSchemePropertiesType];
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull propers, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *key = [propers name];
// NSLog(@"the key is %@",key);
NSString *vl = [ptr valueForKey:key];
RLMPropertyType type = (RLMPropertyType)[typeOj intForKey:key];
if (type == RLMPropertyTypeArray)
{
RLMArray *vlist = (RLMArray *)vl;
RLMObject *obj = [vlist firstObject];
NSString *acsName = [[obj objectSchema] className];
RLMArray *nlist = [[RLMArray alloc] initWithObjectClassName:acsName];
for (NSInteger idx=0; idx<vlist.count; idx++)
{
RLMObject *obj = [vlist objectAtIndex:idx];
NSArray <RLMProperty *>*allKeys = [obj objectSchema].properties;
Class csName = NSClassFromString([[obj objectSchema] className]);
RLMObject *at = [[csName alloc] init];
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull xj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *key = [xj name];
NSString *vl = [NSString stringWithFormat:@"%@",[obj valueForKey:key]];
[at setValue:vl forKey:key];
}];
[nlist addObject:at];
}
[ab setValue:nlist forKey:key];
}
else
{
[ab setValue:vl forKey:key];
}
}];
[ab setUsersTable:accid];
RLMRealm * realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addOrUpdateObject:ab];
[realm commitWriteTransaction];
}
+ (void)save2Dao:(NSArray <IMSessionDaoScheme *>*)objs
{
NSMutableArray *list = [NSMutableArray arrayWithCapacity:0];
[list addObjectsFromArray:objs];
[list enumerateObjectsUsingBlock:^(IMSessionDaoScheme * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
[obj save2Dao];
}];
}
- (void)updateOncePropertyValue:(void (^)(id thisModel))ablock
{
ablock(self);
[self save2Dao];
}
//删
- (void)deleteFromDao
{
if ([self isKindOfClass:[IMSessionDaoScheme class]])
{
[[self class] deleteFromDao:@[self]];
}
else
{
NSAssert(NO, @"instance error");
}
}
+ (void)deleteFromDao:(NSArray <IMSessionDaoScheme *>*)resArr
{
RLMRealm *realm = [RLMRealm defaultRealm];
NSString *accid = [IMDataConfigTools instance].imAccountBlock();
[resArr enumerateObjectsUsingBlock:^(IMSessionDaoScheme * _Nonnull md, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *pkey = [[md class] primaryKey];
NSString *stext = [NSString stringWithFormat:@"usersTable='%@' AND %@='%@'",accid,pkey,[md valueForKey:pkey]];
Class csName = [md class];
RLMResults <IMSessionDaoScheme *>*resArr = [csName objectsWhere:stext];
for (IMSessionDaoScheme *ab in resArr) {
[realm transactionWithBlock:^{
[realm deleteObject:ab];
}];
}
}];
}
+ (void)deleteAll
{
NSString *accid = [IMDataConfigTools instance].imAccountBlock();
Class csName = [self class];
RLMResults <IMSessionDaoScheme *>*resArr = [csName objectsWhere:[NSString stringWithFormat:@"usersTable='%@'",accid]];
RLMRealm *realm = [RLMRealm defaultRealm];
for (IMSessionDaoScheme *ab in resArr) {
[realm transactionWithBlock:^{
[realm deleteObject:ab];
}];
}
}
//查 ifAnd=yes,sql statement will be liked key=1 AND key1=2,other else key=1 OR key1=2....
+ (NSArray <IMSessionDaoScheme *>*)queryFromDaoWithStatement:(NSDictionary *)stej ifAnd:(BOOL)ifAnd sortedKey:(NSString *)sortedKey
{
NSString *accid = [IMDataConfigTools instance].imAccountBlock();
NSDictionary *typeOj = [self daoSchemePropertiesType];
__block NSString *whereSt = nil;
[stej enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop)
{
RLMPropertyType type = (RLMPropertyType)[typeOj intForKey:key];
if (!whereSt)
{
if (type == RLMPropertyTypeInt || type == RLMPropertyTypeBool || type == RLMPropertyTypeFloat || type == RLMPropertyTypeDouble)
{
whereSt = [NSString stringWithFormat:@"%@=%@",key,obj];
}
else
{
whereSt = [NSString stringWithFormat:@"%@='%@'",key,obj];
}
}
else
{
if (ifAnd)
{
if (type == RLMPropertyTypeInt || type == RLMPropertyTypeBool || type == RLMPropertyTypeFloat || type == RLMPropertyTypeDouble)
{
whereSt = [whereSt stringByAppendingFormat:@" AND %@=%@",key,obj];
}
else
{
whereSt = [whereSt stringByAppendingFormat:@" AND %@='%@'",key,obj];
}
}
else
{
if (type == RLMPropertyTypeInt || type == RLMPropertyTypeBool || type == RLMPropertyTypeFloat || type == RLMPropertyTypeDouble)
{
whereSt = [whereSt stringByAppendingFormat:@" OR %@=%@",key,obj];
}
else
{
whereSt = [whereSt stringByAppendingFormat:@" OR %@='%@'",key,obj];
}
}
}
}];
if (whereSt)
{
whereSt = [whereSt stringByAppendingFormat:@" AND usersTable='%@'",accid];
}
else
{
whereSt = [NSString stringWithFormat:@"usersTable='%@'",accid];
}
Class csName = [self class];
RLMResults *resArr = [csName objectsWhere:whereSt];
if (sortedKey && sortedKey.length > 0)
{
NSMutableArray *dpList = [NSMutableArray arrayWithCapacity:0];
NSArray *sp = [sortedKey componentsSeparatedByString:@","];
if (sp.count == 1)
{
NSArray *spChild = [sortedKey componentsSeparatedByString:@" "];
NSString *key = [spChild firstObject];
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
key = [key stringByTrimmingCharactersInSet:set];
NSString *vl = @"desc";
if (spChild.count > 1)
{
vl = [spChild lastObject];
}
vl = [vl stringByTrimmingCharactersInSet:set];
BOOL ascending = [[vl lowercaseString] isEqualToString:@"asc"];
RLMSortDescriptor *dp = [RLMSortDescriptor sortDescriptorWithKeyPath:key ascending:ascending];
[dpList addObject:dp];
}
else
{
[sp enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
obj = [obj stringByTrimmingCharactersInSet:set];
NSArray *spChild = [obj componentsSeparatedByString:@" "];
NSString *key = [spChild firstObject];
key = [key stringByTrimmingCharactersInSet:set];
NSString *vl = [spChild lastObject];
vl = [vl stringByTrimmingCharactersInSet:set];
BOOL ascending = [[vl lowercaseString] isEqualToString:@"asc"];
RLMSortDescriptor *dp = [RLMSortDescriptor sortDescriptorWithKeyPath:key ascending:ascending];
[dpList addObject:dp];
}];
}
resArr = [resArr sortedResultsUsingDescriptors:dpList];
}
NSArray <NSString *>*allKeys = [self allProperties];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:0];
for (NSInteger idx=0; idx<resArr.count; idx++)
{
IMSessionDaoScheme *item = [resArr objectAtIndex:idx];
item.spcyInMainThread = [NSThread isMainThread];
NSObject *at = [[csName alloc] init];
[allKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *vl = [item valueForKeyPath:key];
RLMPropertyType type = (RLMPropertyType)[typeOj intForKey:key];
if (type == RLMPropertyTypeArray)
{
RLMArray *vlist = (RLMArray *)vl;
RLMObject *obj = [vlist firstObject];
NSString *acsName = [[obj objectSchema] className];
RLMArray *nlist = [[RLMArray alloc] initWithObjectClassName:acsName];
for (NSInteger idx=0; idx<vlist.count; idx++)
{
RLMObject *obj = [vlist objectAtIndex:idx];
NSArray <RLMProperty *>*allKeys = [obj objectSchema].properties;
Class csName = NSClassFromString([[obj objectSchema] className]);
RLMObject *at = [[csName alloc] init];
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull xj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *key = [xj name];
NSString *vl = [NSString stringWithFormat:@"%@",[obj valueForKey:key]];
[at setValue:vl forKey:key];
}];
[nlist addObject:at];
}
[at setValue:nlist forKey:key];
}
else
{
[at setValue:vl forKey:key];
}
}];
[arr addObject:at];
}
return arr;
}
+ (NSMutableArray <IMSessionDaoScheme *>*)queryFromDaoWithStatement:(NSDictionary *)stej
{
BOOL flag = [stej allKeys].count>1?YES:NO;
return [self queryFromDaoWithStatement:stej ifAnd:flag sortedKey:nil];
}
+ (NSMutableArray <IMSessionDaoScheme *>*)queryFromDaoWithWhere:(NSString *)sttext
{
NSString *accid = [IMDataConfigTools instance].imAccountBlock();
NSString *st = [NSString stringWithFormat:@"usersTable='%@'",accid];
if (sttext && sttext.length > 0)
{
st = [st stringByAppendingFormat:@" AND %@",sttext];
}
Class csName = [self class];
NSArray <NSString *>*allKeys = [self allProperties];
RLMResults <IMSessionDaoScheme *>*resArr = [csName objectsWhere:st];
NSDictionary *typeOj = [csName daoSchemePropertiesType];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:0];
for (NSInteger idx=0; idx<resArr.count; idx++)
{
IMSessionDaoScheme *item = [resArr objectAtIndex:idx];
item.spcyInMainThread = [NSThread isMainThread];
NSObject *at = [[csName alloc] init];
[allKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *vl = [item valueForKeyPath:key];
RLMPropertyType type = (RLMPropertyType)[typeOj intForKey:key];
if (type == RLMPropertyTypeArray)
{
RLMArray *vlist = (RLMArray *)vl;
RLMObject *obj = [vlist firstObject];
NSString *acsName = [[obj objectSchema] className];
RLMArray *nlist = [[RLMArray alloc] initWithObjectClassName:acsName];
for (NSInteger idx=0; idx<vlist.count; idx++)
{
RLMObject *obj = [vlist objectAtIndex:idx];
NSArray <RLMProperty *>*allKeys = [obj objectSchema].properties;
Class csName = NSClassFromString([[obj objectSchema] className]);
RLMObject *at = [[csName alloc] init];
[allKeys enumerateObjectsUsingBlock:^(RLMProperty * _Nonnull xj, NSUInteger idx, BOOL * _Nonnull stop)
{
NSString *key = [xj name];
NSString *vl = [NSString stringWithFormat:@"%@",[obj valueForKey:key]];
[at setValue:vl forKey:key];
}];
[nlist addObject:at];
}
[at setValue:nlist forKey:key];
}
else
{
[at setValue:vl forKey:key];
}
}];
[arr addObject:at];
}
return arr;
}
+ (NSMutableArray <IMSessionDaoScheme *>*)queryAll
{
return [self queryFromDaoWithWhere:nil];
}
@end
#endif
现在来说说,我们为什么要解耦?realm查询、更新等操作后会让你传入的model变成其托管的类,从类名就能看出来,如下图所示:

如果查询数据是在子线程查询 ,那么我们在主线程使用时,直接崩溃。如上图,类名RLM:Managed 开头,只要这样的数据被我们保存到数据容器稍后跨线程使用的时候直接崩溃。
而我们要实现的是读写均由realm操作,但是我们的数据model的使用,仍然独立于Realm,不受其托管线程管理限制,保证我们的数据模型使用是安全的,随时跨线程使用。如下图,最终返回到我们业务页面或者控制器时是非realm托管的对象模型,这样我们就可以任意跨线程使用。数据模型中的值有变化修改时,我们直接使用- (void)save2Dao再次保存,并且保存后保证我们传入的model不被realm托管,做到数据隔离及线程使用安全。

如何使用呢?只需要让你的model继承自IMSessionDaoScheme即可。

查询删除如下:
CDMessageDraftModel *emd = (CDMessageDraftModel *)[[CDMessageDraftModel queryFromDaoWithStatement:@{@"timelineId":self.model.timelineId}] firstObject];
[emd deleteFromDao];以上也只是抛砖引玉,给大家提供一个解决思路,若有更好的建议及使用经验 ,请大家不吝赐教。
边栏推荐
- 畢業回饋!Apache Doris 社區所有貢獻者來領禮品啦!
- Talk about MySQL's locking rule "hard hitting MySQL series 15"
- A piece of code to solve the problem of automatic disconnection of Google colab
- Which methods are not allowed to be overridden?
- 《MATLAB 神经网络43个案例分析》:第29章 极限学习机在回归拟合及分类问题中的应用研究——对比实验
- Yarn application submission process
- DeformConv
- long start = currentTimeMillis();
- Please, use three JS make 2D pictures have 3D effect cool, OK
- Detailed explanation of deep learning technology for building an image search engine that can find similar images
猜你喜欢

汉诺塔问题

Kubernetes - bare metal cluster environment

JS regular expression to implement the thousands separator

1108. Defanging an IP Address

Squoosh - Google's free open source image compression tool, reducing the image size by 90%! Support API development calls

Kubernetes——裸机搭建集群环境

【云原生】2.2 kubeadm创建集群

Kubernetes——部署应用到集群中

C#中Cookie设置与读取

【chrome】 谷歌小技巧 谷歌浏览器 自带 滚动截图(全屏截图)
随机推荐
Service migration when deploying SuperMap iserver war package
风阀执行EN 1634-1耐火测试完整性能坚持 4小时吗?
2022 new test questions for tea specialists (primary) and summary of tea specialists (primary) examination
Some notes on the use of C language strings
P1061 [NOIP2006 普及组] Jam 的计数法
php正则怎么去掉括号内容
postmanUtils工具类,模拟postman的get,post请求
js正则表达式实现千分位符
C language data type conversion rules (implicit conversion + explicit conversion)
jedispool的使用
不允许方法被重写的方式包括哪些?
Non recursive printing Fibonacci sequence
Accelerate the promotion of industrial Internet, and map out a new blueprint for development
Some considerations of C language custom function
Opencv function usage details 1~10, including code examples
DeformConv
Flink deployment mode (I) - standalone and Application
新手开店货源怎么找,怎么找到优质货源?
YARN 应用提交过程
记本地项目启动报错:无效的源发行版: 8