美文网首页
Realm数据查询修改踩坑之路

Realm数据查询修改踩坑之路

作者: 星星杨 | 来源:发表于2022-11-22 22:32 被阅读0次

    由于项目开始阶段为了图方便,所以数据存本地都采用了归档的方式,归档这种方式,操作简便,代码也很快,但是缺点就是稍微有点改动就需要把所有数据再归档一遍,费时费力,针对少量数据可以这么做,但是随着归档的数据越来越多,每次归档的时间也就越来越长了,所以数据库替换是势在必行;
    由于之前都是对模型归档,所以进行数据库升级主要考虑的是CoreData跟Realm,经过一番比对之后,还是选择了Realm,它的优势就不多说了,支持OC、Swift、java,可以同时跨Android、iOS使用,具体可以参考下面几个帖子;
    https://www.jianshu.com/p/5c5931f61600
    https://blog.csdn.net/weixin_33691817/article/details/87958683
    https://www.jianshu.com/p/50e0efb66bdf
    这边主要对替换数据库遇到的几个问题做一下简单的记录;

    1、数据类型问题

    Realm支持的数据类型:
    BOOL、int、NSInteger、long、long long、float、double、NSString、NSDate、NSNumber ;
    所以对于CGRect、UIImage等数据都不支持,需要转换一下

    - (void)setContentFrame:(CGRect)contentFrame {
        _contentFrame = contentFrame;
        _x = contentFrame.origin.x;
        _y = contentFrame.origin.y;
        _w = contentFrame.size.width;
        _h = contentFrame.size.height;
    }
    
    - (CGRect)contentFrame {
        if (CGRectEqualToRect(_contentFrame, CGRectZero) && _w > 0) {
            _contentFrame = CGRectMake(_x, _y, _w, _h);
        }
        return _contentFrame;
    }
    
    - (UIImage *)sourceImage {
        if (!_sourceImage && _sourceData) {
            _sourceImage = [UIImage imageWithData:_sourceData];
        }
        return _sourceImage;
    }
    
    - (void)setSourceImage:(UIImage *)sourceImage {
        _sourceImage = sourceImage;
        _sourceData = sourceImage ? UIImageJPEGRepresentation(sourceImage, 0.5) : nil;
    }
    
    - (UIImage *)backImage {
        if (!_backImage && _backImageData) {
            _backImage = [UIImage imageWithData:_backImageData];
        }
        return _backImage;
    }
    
    - (void)setBackImage:(UIImage *)backImage {
        _backImage = backImage;
        _backImageData = backImage ? UIImageJPEGRepresentation(backImage, 0.5) : nil;
    }
    
    // Specify properties to ignore (Realm won't persist these)
    // 忽略的属性赋值
    + (NSArray *)ignoredProperties {
        return @[@"backImage",@"sourceImage",@"contentFrame"];
    }
    

    2、数据查询问题

    由于我们数据结构的特殊性,存储的数据需要进一步处理才能渲染,所以这边就需要把数据库查询到的数据另外缓存处理;
    很奇怪的是,想要使用查询到的数据,一旦把它取出来另外存储,就会没有数据,而且跟原来数据库中存储的对象地址不一样;



    检查发现是无论使用forin方法遍历,还是objectIndex方法查询,最后都会返回一个新的对象,跟可知道打印出来的数据不一样;


    通过控制台尝试修改属性值



    虽然打印有报错,但是最后结果还是赋值成功了;



    所以就有了第一个想法,通过属性拷贝的方式取出数据;
     // 数据拷贝
    - (id)mutableCopy {
        unsigned int count = 0;
        Class class = self.class;
        id model = [class new];
        // 数据
        while (class && class != [RLMObject class]) {
            Ivar * ivars = class_copyIvarList(class, &count);
            for (int i = 0; i < count; i++) {
                Ivar ivar = ivars[i];
                const char * name = ivar_getName(ivar);
                NSString * key = [NSString stringWithUTF8String:name];
                //设置到成员变量身上
                [model setValue:[self valueForKey:key] forKey:key];
            }
            
            free(ivars);
            class = class.superclass;
        }
        return model;
    }
    

    但是此方法最后也是铩羽而归,使用result[i]势必会走到init方法,所以,最后copy出来的还是空数据;

    控制台突破口

    上面有提到,通过控制台打印可以正常查看到数据,那么它内部肯定是对描述做了处理;



    po [result description] 结果也是一样的



    尝试进入内部查看

    断点到具体的方法里面,那么接下来就是看一下内部是如何获取到数据的



    到这一步发现它内部也是调用了forin遍历results;

    遍历数据内部对数据做了排序,但是最后走回到了get->create->init,可见Realm设计是不希望外部拿到原始数据,难道只能在元数据上做处理🤔

    obj内部数据都是空,但是sub描述已经赋上值了,那么descriptionWithMaxDepth里面肯定是暗藏玄机;


    descriptionWithMaxDepth


    到这一步就比较明朗了,ClassInfo里面的properties保存的所有存储的属性对象RLMProperty,通过objectForKeyedSubscript找出value;这样我们就可以参考这思路把我们想要的值赋值上去了;


    下面的判断是做递归操作,如果value还是RLMObject对象,那么就继续深入下一层取值,这里打印depth最多是5层,我们要把值都取出来展示渲染,所以这个层级我们可以尽量多一点;

    if ([object respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
                sub = [object descriptionWithMaxDepth:depth - 1];
            }
    

    顺便提一下这个objectForKeyedSubscript,


    @property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
    

    就是通过_allPropertiesByName进行保存的映射关系查询,每次更新属性就会刷新这个map字典;


    综合以上之后,就能从realm管理的对象中深拷贝一份新的数据供我们使用了,核心代码附上

    /**
     * 读取缓存素材
     */
    + (NSArray *)getSaveData {
        DLog(@"素材解档=========开始");
        NSArray *info = [NSKeyedUnarchiver unarchiveObjectWithFile:SouceCachesDirectory];
        DLog(@"素材解档=========结束");
        if (info) {
            // 之前归档的数据迁移
            [self reSaveInfoToRealm:info];
            [FileTools deleteFileAtPath:SouceCachesDirectory];
        }
        DLog(@"读取数据库数据=========开始");
        NSMutableArray *resultData = [NSMutableArray array];
        RLMResults <EMSourceCellModel *>*result = [EMSourceCellModel allObjects];
        RLMResults *sortResult = [result sortedResultsUsingKeyPath:@"ID" ascending:NO];
        NSLog(@"%@",[sortResult description]);
        for (id obj in sortResult) {
            EMSourceCellModel *model = [EMSourceCellModel new];
            [self getDepthDataWithObj:obj resultModel:model];
            [resultData addObject:@[model]];
        }
        DLog(@"读取数据库数据=========结束");
        return resultData;
    }
    
    + (void)getDepthDataWithObj:(RLMObjectBase *)obj resultModel:(id)model {
        NSArray *properties = [[obj valueForKey:@"_objectSchema"] valueForKey:@"properties"];
        for (RLMProperty *property in properties) {
            id object = [(id)obj objectForKeyedSubscript:property.name];
            if ([object isKindOfClass:[RLMObject class]]) {
                [self getDepthDataWithObj:object resultModel:model];
            } else {
                [model setValue:object forKey:property.name];
            }
        }
    }
    
    

    3、更新数据

    正如上文所说的,通过唯一值ID查询的results数据,使用firstObject获取到的数据仍然是空的,所以更新这一块也同样遇到了查询一样的问题;



    此处我也想不到更好的办法,只能使用最蠢的方式,如果有查询到就先delete掉,然后在重新add;
    修改完之后,我的增删改的代码就变成了下面的样子;

    - (void)add {
        [self update:true needRemove:false];
    }
    
    - (void)update {
        [self update:true needRemove:true];
    }
    
    - (void)remove {
        [self update:false needRemove:true];
    }
    
    - (void)update:(BOOL)needAdd needRemove:(BOOL)needRemove {
        NSLog(@"修改素材数据成功11111111");
        //更新数据方式一:根据条件取出model
        EMSourceCellModel *model = nil;
        if (needRemove) {
            NSString *condition = [NSString stringWithFormat:@"ID = %lld",self.ID];
            RLMResults * result = [EMSourceCellModel objectsWhere:condition];
            model = result.firstObject;
        }
    
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            if (model && needRemove) {
                [realm deleteObject:model];
            }
            if (needAdd) {
               // 不能直接add本身,因为直接add的对象,由realm直接管理,外面没加事务锁,操作外面数据就会崩,用空间换时间,懒得每个修改的地方都去加锁              
               // Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first
                [realm addObject:[self mutableCopy]];
            }
            
            NSLog(@"修改素材数据成功22222222");
        }];
        RLMResults *result1 = [EMSourceCellModel allObjects];
        NSLog(@"result.count == %lu",(unsigned long)result1.count);
        NSLog(@"修改素材数据成功33333333");
    }
    

    我这种操作可能不是Realm官方所支持的操作,虽然安全性降低了,但是灵活性增加了,针对于我们这种本地素材图片图像类的缓存,其实灵活性比安全性可操作性更强一点;

    4、修改前后对比

    同样添加15张图片,效果对比;

    磁盘缓存大小

    归档15张图片是150M的磁盘缓存



    Realm NSData存储是18MB左右,这多少可能跟这里image转data做了较大程度压缩有一点关系;


    =

    内存占用大小对比

    归档过程中,内存占用跟CPU使用率都会飙升



    Realm操作数据过程中,内存没有浮动,整体内存占用率也偏低很多,当然也可能跟我对图片做了压缩有关系;


    增删改时间对比

    归档15个内容,修改一次数据,大概是7秒左右时间



    同样的数据,Realm删除、增加各操作一次的时间大概是2-5毫秒左右,查询all的时间大概需要200毫秒


    以上,就是我在使用Realm过程中遇到的一些问题,以及自己的一些个人愚见,如有不妥,欢迎指正;

    2022-11-24补充修改,子类继承问题

    涉及到子类的问题,因为Realm是根据classname进行读取查询的,所以如果一组数据包括好几种类型的model,就会有问题,比如我的素材普通素材,还有画中画素材,画中画素材继承于普通素材,所以要想把数据全部取出来,就不能简单的直接取了,下面附上修改后的完整代码;

    - (long long)ID {
        if (!_ID) {
            _ID = [[NSDate date] timeIntervalSince1970]*1000 + random()%1000;
        }
        return _ID;
    }
    
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {}
    //- (void)setCustomValue:(id)value forKey:(NSString *)key {
    //    if (value) {
    //        [self setCustomValue:value forKey:key];
    //    } else {
    ////        NSLog(@"---------- %s Crash Because Method %s  ---------- key=%@\n", class_getName(self.class), __func__,key);
    //    }
    //}
    
    //+ (NSString *)primaryKey {
    //    return @"ID";
    //}
    
    - (void)setContentFrame:(CGRect)contentFrame {
        _contentFrame = contentFrame;
        _x = contentFrame.origin.x;
        _y = contentFrame.origin.y;
        _w = contentFrame.size.width;
        _h = contentFrame.size.height;
    }
    
    - (CGRect)contentFrame {
        if (CGRectEqualToRect(_contentFrame, CGRectZero) && _w > 0) {
            _contentFrame = CGRectMake(_x, _y, _w, _h);
        }
        return _contentFrame;
    }
    
    - (UIImage *)sourceImage {
        if (!_sourceImage && _sourceData) {
            _sourceImage = [UIImage imageWithData:_sourceData];
        }
        return _sourceImage;
    }
    
    - (void)setSourceImage:(UIImage *)sourceImage {
        _sourceImage = sourceImage;
        _sourceData = sourceImage ? UIImageJPEGRepresentation(sourceImage, 0.5) : nil;
    }
    
    - (UIImage *)backImage {
        if (!_backImage && _backImageData) {
            _backImage = [UIImage imageWithData:_backImageData];
        }
        return _backImage;
    }
    
    - (void)setBackImage:(UIImage *)backImage {
        _backImage = backImage;
        _backImageData = backImage ? UIImageJPEGRepresentation(backImage, 0.5) : nil;
    }
    
    - (void)add {
        [self update:true needRemove:false];
    }
    
    - (void)update {
        [self update:true needRemove:true];
    }
    
    - (void)remove {
        [self update:false needRemove:true];
    }
    
    - (void)update:(BOOL)needAdd needRemove:(BOOL)needRemove {
        NSLog(@"修改素材数据成功11111111");
        //更新数据方式一:根据条件取出model
        EMSourceCellModel *model = nil;
        if (!self.ID) {
            DLog(@"生成ID成功");
        }
        if (needRemove) {
            NSString *condition = [NSString stringWithFormat:@"ID = %lld",self.ID];
    //        RLMResults * result = [EMSourceCellModel objectsWhere:condition];
            RLMResults * result = [self.class objectsWhere:condition];
            model = result.firstObject;
        }
    
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            if (model && needRemove) {
                [realm deleteObject:model];
            }
            if (needAdd) {
                // 不能直接add本身,因为直接add的对象,由realm直接管理,外面没加事务锁,操作外面数据就会崩,用空间换时间,懒得每个修改的地方都去加锁
                // Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first
                [realm addObject:[self mutableCopy]];
            }
            
            NSLog(@"修改素材数据成功22222222");
        }];
    //    RLMResults *result1 = [EMSourceCellModel allObjects];
    //    NSLog(@"result.count == %lu",(unsigned long)result1.count);
    //    NSLog(@"修改素材数据成功33333333");
    }
    
    /**
     * 归档
     */
    + (void)save:(NSArray *)info {
        [NSKeyedArchiver archiveRootObject:info toFile:SouceCachesDirectory];
    }
    
    // 数据迁移
    + (void)reSaveInfoToRealm:(NSArray *)info {
        if (info && info.count > 0) {
            for (NSArray *sectionArr in info) {
                EMSourceCellModel *model = sectionArr.firstObject;
                if (model) {
                    EMSourceCellModel *reModel = [model mutableCopy];
                    [reModel add];
                }
            }
        }
    }
    
    /**
     * 读取缓存素材
     */
    + (NSArray *)getSaveData {
        DLog(@"素材解档=========开始");
        NSArray *info = [NSKeyedUnarchiver unarchiveObjectWithFile:SouceCachesDirectory];
        DLog(@"素材解档=========结束");
        if (info) {
            // 之前归档的数据迁移
            [self reSaveInfoToRealm:info];
            [FileTools deleteFileAtPath:SouceCachesDirectory];
        }
        DLog(@"读取数据库数据=========开始");
        NSMutableArray *resultData = [NSMutableArray array];
        [resultData addObjectsFromArray:[self readSourceData:EMSourceCellModel.className]];
        [resultData addObjectsFromArray:[self readSourceData:EMSourceVideoCellModel.className]];
        resultData = (NSMutableArray *)[EMTool arrayWithSort:resultData key:@"ID" ascending:NO];
        DLog(@"读取数据库数据=========结束");
        return resultData;
    }
    
    + (NSArray *)readSourceData:(NSString *)className {
        Class sourceClass = objc_getClass([className UTF8String]);
        NSMutableArray *resultData = [NSMutableArray array];
        if ([sourceClass respondsToSelector:@selector(allObjects)]) {
            RLMResults *result = [sourceClass performSelector:@selector(allObjects)];
            for (id obj in result) {
                EMSourceCellModel *model = [sourceClass new];
                [self getDepthDataWithObj:obj resultModel:model];
                [resultData addObject:model];
            }
        }
        
        return resultData;
    }
    
    + (void)getDepthDataWithObj:(RLMObjectBase *)obj resultModel:(id)model {
        NSArray *properties = [[obj valueForKey:@"_objectSchema"] valueForKey:@"properties"];
        for (RLMProperty *property in properties) {
            id object = [(id)obj objectForKeyedSubscript:property.name];
            if ([object isKindOfClass:[RLMObject class]]) {
                [self getDepthDataWithObj:object resultModel:model];
            } else {
                [model setValue:object forKey:property.name];
            }
        }
    }
    
    // 数据拷贝
    - (id)mutableCopy {
        unsigned int count = 0;
        Class class = self.class;
        id model = [class new];
        // 数据
        while (class && class != [RLMObject class]) {
            Ivar * ivars = class_copyIvarList(class, &count);
            for (int i = 0; i < count; i++) {
                Ivar ivar = ivars[i];
                const char * name = ivar_getName(ivar);
                NSString * key = [NSString stringWithUTF8String:name];
                //设置到成员变量身上
                [model setValue:[self valueForKey:key] forKey:key];
            }
            
            free(ivars);
            class = class.superclass;
        }
        return model;
    }
    
    //解档
    - (instancetype)initWithCoder:(NSCoder *)coder {
        if ((self = [super modelInitWithCoder:coder])) {
            unsigned int count = 0;
            Class class = self.class;
            // 层层解档档,父类数据也要取出
            while (class && class != [NSObject class]) {
                Ivar * ivars = class_copyIvarList(class, &count);
                for (int i = 0; i < count; i++) {
                    Ivar ivar = ivars[i];
                    const char * name = ivar_getName(ivar);
                    NSString * key = [NSString stringWithUTF8String:name];
                    //解档
                    id value = [coder decodeObjectForKey:key];
                    //设置到成员变量身上
                    [self setValue:value forKey:key];
                }
                
                free(ivars);
                class = class.superclass;
            }
        }
        return self;
    }
    
    - (void)setValue:(id)value forKey:(NSString *)key {
        if (value) {
            if ([key isEqualToString:@"_contentFrame"]) {
                self.contentFrame = [value CGRectValue];
            } else if ([key isEqualToString:@"_backImage"]) {
                self.backImage = value;
            } else if ([key isEqualToString:@"_sourceImage"]) {
                self.sourceImage = value;
            } else {
                [super setValue:value forKey:key];
            }
        }
    }
    
    // Specify properties to ignore (Realm won't persist these)
    // 忽略的属性赋值
    + (NSArray *)ignoredProperties {
        return @[@"backImage",@"sourceImage",@"contentFrame"];
    }
    

    相关文章

      网友评论

          本文标题:Realm数据查询修改踩坑之路

          本文链接:https://www.haomeiwen.com/subject/wfrnxdtx.html