美文网首页iOS常用
iOS开发之realm本地存储

iOS开发之realm本地存储

作者: 刘铁崧 | 来源:发表于2020-10-18 23:45 被阅读0次

    git地址:https://github.com/realm/realm-cocoa
    官网地址:https://realm.io/
    xcode工具包:https://github.com/realm/realm-cocoa
    realm studio下载地址:https://github.com/realm/realm-studio

    1.准备阶段

    配置开发环境:pod比较简单
    如果单元测试中要使用要记得在单元测试target中同样引入库

    # Uncomment the next line to define a global platform for your project
    # platform :ios, '9.0'
    
    target 'TestRealm' do
      # Comment the next line if you don't want to use dynamic frameworks
      use_frameworks!
      pod 'Realm'
      # Pods for TestRealm
    
      target 'TestRealmTests' do
        inherit! :search_paths
        pod 'Realm'
        # Pods for testing
      end
    
      target 'TestRealmUITests' do
        # Pods for testing
      end
    
    end
    
    

    安装数据库管理工具:Real studio(https://github.com/realm/realm-studio

    ![截屏2020-09-22 下午8.24.35.png](https://img.haomeiwen.com/i3692367/fca95f008ef452cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    安装xcode插件包:(官网就有)


    2.基本使用

    2.1 引入文件头

    #import <Realm/Realm.h>
    

    2.2 利用插件创建模型类


    模型类中引入属性要注意写法

    
    #import <Realm/Realm.h>
    
    @interface Student : RLMObject
    @property int student_number;
    @property NSString *name;
    @end
    
    // This protocol enables typed collections. i.e.:
    // RLMArray<Student *><Student>
    RLM_ARRAY_TYPE(Student)
    

    .m文件中设置通过复写primaryKey方法设置主键

    + (NSString *)primaryKey{
        return "student_number";
    }
    

    2.3 保存写入数据

        // 可以用数组,也可以用字典,(如果用数组要保证和模型一致)
    //    Student *student = [[Student alloc]initWithValue:@{@"student_number":@1,@"name":@"cy"}];
        Student *student = [[Student alloc]initWithValue:@[@1,@"cy"]];
        RLMRealm *realm = [RLMRealm defaultRealm];
    
       //普通写法
       // [realm beginWriteTransaction];// 开始写入
       // [realm addObject:student];// 存储数据
        //[realm comm itWriteTransaction];//提交
      
        // 也可以这样写
        [realm transactionWithBlock:^{
                [realm addObject:student];
        }];
    

    也可以用不用初始化数据模型用以下方法实现

    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm transactionWithBlock:^{
      [Student createInDefaultRealmWithValue:@[@2,@"liutiesong"]];
    }];
    

    通过realm studio打开文件查看写入数据


    2.4 更新数据

    注:

    • 更新数据前要先写入数据
    • 更新的数据模型对象必须是被realm所持有的对象[图片上传中...(截屏2020-09-27 下午12.38.23.png-45f614-1601182593843-0)]
        Student *student = [[Student alloc]initWithValue:@[@3,@"test"]];
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            [realm addObject:student];
        }];
        [realm transactionWithBlock:^{
            student.name = @"finalTest";
        }];
    
    addObject时输出
    最后更新输出

    2.5 查找数据并更新

        RLMRealm *realm = [RLMRealm defaultRealm];
        
        RLMResults *result = [Student objectsWhere:@"name = 'finalTest'"];
        Student *student = result.firstObject;
        [realm transactionWithBlock:^{
                student.name = @"cy";
        }];
    

    2.6 添加/创建 并 更新/覆盖 数据

    注意:

    • 必须设置主键否则会报错(添加方法看起始步骤)
    • 添加数据时如果主键相同,则会进行修改
    添加更新数据
        Student *student = [[Student alloc]initWithValue:@[@2,@"test-2"]];
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
                [realm addOrUpdateObject:student];
        }];
    
    创建更新数据
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            [Student createOrUpdateInRealm:realm withValue:@[@2,@"test2"]];
        }];
    

    2.8 删除数据

    2.8.1 查找出指定数据并删除

        RLMRealm *realm = [RLMRealm defaultRealm];
        RLMResults *result = [Student objectsWhere:@"name = 'test2'"];
        Student *student = result.firstObject;
        [realm transactionWithBlock:^{
            [realm deleteObject:student];
        }];
    

    2.8.2 删除库内所有数据

        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            [realm deleteAllObjects];
        }];
    

    2.8.3 删除制定模型库内所有数据

    RLMRealm *realm = [RLMRealm defaultRealm];
        RLMResults *results = [Student allObjects];
        for (Student *student in results) {
            [realm transactionWithBlock:^{
                 [realm deleteObject:student];
            }];
        }
    

    2.8.4 根据主键删除制定模型

        Student *student = [Student objectInRealm:realm forPrimaryKey:@2];
        [realm transactionWithBlock:^{
            [realm deleteObject:student];
        }];
    

    2.9 查询

    2.9.1 查询所有数据

        RLMResults *studentResult = [Student allObjects];
        NSLog(@"%@",studentResult);
    

    2.9.2 条件查询

        RLMResults *result = [Student objectsWhere:@"name = 'test2'"];
    

    2.9.3 参照属性排序(将查询出来的结果根据参数进行排序)

        RLMResults<Student*> *studentResult = [Student allObjects];
        RLMResults *sortedResult  = [studentResult sortedResultsUsingKeyPath:@"name" ascending:YES];
    

    2.9.4 链式查询

    可以支持多条件链式叠加查询

        RLMResults<Student*> *studentResult = [Student objectsWhere:@"student_number > 2"];
        RLMResults *secondStudentResults = [studentResult objectsWhere:@"student_number < 4"];
    

    2.9.5 分页查询

        RLMResults *allStudents = [Student allObjects];
        int limit = 2;
        int offset = 1;
        for (int i = offset; i<offset + limit; i++) {
            Student *student = allStudents[i];
            NSLog(@"%@",student);
        }
    

    3.其他使用注意

    3.1 使用模型存储 图片数据

    在属性声明时,realm不支持的类型可以在.m文件中复写ignore方法,或者使用readonly声明

    @property (readonly)UIImage *image;
    @property NSData *imageData;
    
    + (NSArray *)ignoredProperties
    {
        return @[@"image"];
    }
    - (UIImage *)image{
        return [UIImage imageWithData:self.imageData];;
    }
    

    ↑当需要读取UIImage时,可以重写image的set方法来获取图片数据(在数据模型的.m文件中)

    数据存储代码:

        // 可以用数组,也可以用字典,(如果用数组要保证和模型一致)
        RLMRealm *realm = [RLMRealm defaultRealm];
        Student *student = [[Student alloc]init];
        student.name = @"cy";
        student.student_number = 1;
        NSString *filePath = [[NSBundle mainBundle]pathForResource:@"1.jpg" ofType:nil];
        student.imageData = [NSData dataWithContentsOfFile:filePath];
        [realm transactionWithBlock:^{
                [realm addOrUpdateObject:student];
        }];
    

    4. Realm中的表对应关系

    • realm中不可直接使用NSArray,如要使用列表需要使用RLMArray类型
    • RLMArray类型的数据里面存储的对象必须是继承自RLMObject类型的属性
      student.h类中:
    #import "StudentDetails.h"
    @property RLMArray <StudentDetails*><StudentDetails>*details;
    

    创建一个RLMObject类型的模型用来存放数组内数据:

    #import <Realm/Realm.h>
    
    @interface StudentDetails : RLMObject
    @property NSString *detailID;
    @end
    
    // This protocol enables typed collections. i.e.:
    // RLMArray<StudentDetails *><StudentDetails>
    RLM_ARRAY_TYPE(StudentDetails)
    

    4.1 一对一关系

    创建两个数据模型Zoo和Cow,Zoo中包含一个Cow对象如下:

    • Zoo类
    #import <Realm/Realm.h>
    #import "Cow.h"
    @interface Zoo : RLMObject
    @property int zooId;
    @property NSString *name;
    @property Cow *cow;
    @end
    RLM_ARRAY_TYPE(Zoo)
    
    • Cow类
    #import <Realm/Realm.h>
    @interface Cow : RLMObject
    @property int cowId;
    @property NSString *name;
    @end
    RLM_ARRAY_TYPE(Cow)
    

    设置对应的 cowId和zooId为主键

    • 添加数据:
        Zoo *zoo = [Zoo new];
        zoo.zooId = 1;
        zoo.name = @"木有鱼丸";
        Cow *cow = [Cow new];
        cow.cowId = 1;
        cow.name = @"奶牛1号";
        zoo.cow = cow;
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
                [realm addObject:zoo];
        }];
    

    4.2 一对多关系

    • zoo类
    #import <Realm/Realm.h>
    #import "Cow.h"
    @interface Zoo : RLMObject
    @property int zooId;
    @property NSString *name;
    @property RLMArray<Cow *><Cow> *cows;
    @end
    RLM_ARRAY_TYPE(Zoo)
    
    • cow类不变同上
    • 添加数据
        Zoo *zoo = [Zoo new];
        zoo.zooId = 1;
        zoo.name = @"木有鱼丸";
        Cow *cow = [Cow new];
        cow.cowId = 1;
        cow.name = @"奶牛1号";
        Cow *cow2 = [Cow new];
        cow2.cowId = 2;
        cow2.name = @"奶牛2号";
        [zoo.cows addObject:cow];
        [zoo.cows addObject:cow2];
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
                [realm addObject:zoo];
        }];
    
    • 数据库:



    4.3 反向关系

    • zoo类同上不变
    • cow类如下

    1.使用@class声明避免循环引用,使用RLMLinkingObjects创建zoo属性

    #import <Realm/Realm.h>
    @class Zoo;
    @interface Cow : RLMObject
    @property int cowId;
    @property NSString *name;
    @property(readonly) RLMLinkingObjects *zoo;
    @end
    RLM_ARRAY_TYPE(Cow)
    

    2.m文件中通过协议方法构建反向关系

    #import "Cow.h"
    
    @implementation Cow
    + (NSString *)primaryKey{
        return @"cowId";
    }
    // 关联方法
    + (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties{
        // 通过属性描述器关联
        return @{@"zoo":[RLMPropertyDescriptor descriptorWithClass:NSClassFromString(@"Zoo") propertyName:@"cows"]};
    }
    @end
    

    测试获取数据

        Zoo *zoo = [Zoo allObjects].firstObject;
        NSLog(@"打印数据%@",zoo.cows.firstObject.zoo);
    
    2020-09-29 18:47:53.510317+0800 TestRealm[30907:35745028] 打印数据RLMLinkingObjects<Zoo> <0x7fbf92c1c740> (
        [0] Zoo {
            zooId = 1;
            name = 木有鱼丸;
            cows = RLMArray<Cow> <0x60000322f340> (
                [0] Cow {
                    cowId = 1;
                    name = 奶牛1号;
                },
                [1] Cow {
                    cowId = 2;
                    name = 奶牛2号;
                }
            );
        }
    )
    

    5. Realm中的属性

    5.1强制非空

    默认情况下,属性是可空的,如果想要强制要求某个属性必须非空可以如下操作:

    + (NSArray<NSString *> *)requiredProperties{
        return @[@"name"];;
    }
    

    5.2 给属性自定义默认值

    + (NSDictionary *)defaultPropertyValues{
        return @{@"name":@"数据不存在"};
    }
    

    5.3 忽略存储属性,过滤掉不需要存储的属性

    • 方法一:(直接使用readonly声明属性)
    @property(readonly) NSString *name;
    
    • 方法二:.m中复写协议方法
    + (NSArray<NSString *> *)ignoredProperties{
        return @[@"name"];;
    }
    

    6.realm中的消息通知

    • 监听添加数据:
    @property(nonatomic,strong) RLMNotificationToken *token;
    
    - (void)setUp{
        [super setUp];
        RLMRealm *realm = [RLMRealm defaultRealm];
        self.token = [realm addNotificationBlock:^(RLMNotification  _Nonnull notification, RLMRealm * _Nonnull realm) {
            NSLog(@"监听到通知");
        }];
    }
    
    - (void)tearDown{
        [self.token invalidate];
        [super tearDown];
    }
    
    - (void)testOneToOne{
        Cow *cow = [[Cow alloc]initWithValue:@{@"cowId":@1,@"name":@"test"}];
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
                [realm addObject:cow];
        }];
    }
    
    • 监听result查询数据
    @property(nonatomic,strong) RLMNotificationToken *token;
    - (void)setUp{
        [super setUp];
        RLMResults *result = [Cow allObjects];
        self.token = [result addNotificationBlock:^(RLMResults * _Nullable results, RLMCollectionChange * _Nullable change, NSError * _Nullable error) {
            NSLog(@"%@____%@___%@",results,change,error);
        }];
    }
    
    - (void)tearDown{
        [self.token invalidate];//注销监听
        [super tearDown];
    }
    
    - (void)testOneToOne{
        Cow *cow = [[Cow alloc]initWithValue:@{@"cowId":@1,@"name":@"test"}];
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
                [realm addObject:cow];
        }];
    }
    

    可以在RLMCollectionChange中获取具体修改的内容

    7. realm中的多用户数据库区分机制

    创建数据模型

    #import <Realm/Realm.h>
    @interface UserData : RLMObject
    @property int userDataId;
    @end
    RLM_ARRAY_TYPE(UserData)
    

    通过修改尾缀根据用户区分数据库

    - (void)testExample {
        [self setDefalutRealmForUser:@"test1"];
        RLMRealm *realm = [RLMRealm defaultRealm];
        UserData *data = [UserData new];
        data.userDataId = 1;
        [realm transactionWithBlock:^{
            [realm addObject:data];
        }];
        [self setDefalutRealmForUser:@"test2"];
        RLMRealm *realm2 = [RLMRealm defaultRealm];
        UserData *data2 = [UserData new];
        data2.userDataId = 1;
        [realm2 transactionWithBlock:^{
            [realm2 addObject:data2];
        }];
    }
    
    - (void)setDefalutRealmForUser:(NSString*)userId{
        RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
        config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]URLByAppendingPathComponent:userId] URLByAppendingPathExtension:@"realm"];
        [RLMRealmConfiguration setDefaultConfiguration:config];
    }
    

    8.以只读方式打开数据库

    将config中的readObly参数设置为YES

    - (void)testReadOnly{
        RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
        config.readOnly = YES;
        RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
        UserData *data = [UserData new];
        data.userDataId = 100;
        [realm transactionWithBlock:^{
                [realm addObject:data];
        }];    
    }
    

    9. 数据库文件删除

    • 删除test1数据库文件
    - (void)testDeleteDataBase{
        [self setDefalutRealmForUser:@"test1"];
        NSFileManager *manager = [NSFileManager defaultManager];
        RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
        NSArray<NSURL*> *realmFileURLS = @[
            config.fileURL,
            [config.fileURL URLByAppendingPathExtension:@"lock"],
            [config.fileURL URLByAppendingPathExtension:@"log_a"],
            [config.fileURL URLByAppendingPathExtension:@"log_b"],
            [config.fileURL URLByAppendingPathExtension:@"note"]
        ];
        for (NSURL *url in realmFileURLS) {
            NSError *error = nil;
            [manager removeItemAtURL:url error:&error];
            if (error) {
                //容错处理
            }
        }
    }
    

    10.数据库迁移

    10.1 数据库结构迁移
    • 数据模型修改(添加userName属性)
    #import <Realm/Realm.h>
    @interface UserData : RLMObject
    @property int userDataId;
    @end
    RLM_ARRAY_TYPE(UserData)
    
    #import <Realm/Realm.h>
    @interface UserData : RLMObject
    @property int userDataId;
    @property NSString *userName;
    @end
    RLM_ARRAY_TYPE(UserData)
    
    • 数据库迁移配置(一般放在appdelegate中,每次修改要更新数据库版本号)
    - (void)setUp{
        [super setUp];
        RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
        // 叠加版本号(每次更新版本号要比上一次高)
        int newVersion = 1;
        config.schemaVersion = newVersion;
        // 数据迁移
        [config setMigrationBlock:^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
                if (oldSchemaVersion < newVersion) {
                    // 不需要进行操作,自动完成数据结构、数据迁移
                    NSLog(@"需要迁移数据库");
                }
        }];
        // 配置生效
        [RLMRealmConfiguration setDefaultConfiguration:config];
        // 立即迁移
        [RLMRealm defaultRealm];
    }
    
    • 数据迁移操作
    - (void)testMigration{
        UserData *user = [UserData new];
        user.userDataId = 1;
        user.userName = @"cy";
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            [realm addObject:user];
        }];
    }
    
    数据库迁移前
    数据库迁移后
    • 合并或保留旧数据:
      原数据库数据:



      迁移后数据:



      ** 代码 **
      模型层修改
    #import <Realm/Realm.h>
    @interface UserData : RLMObject
    @property int userDataId;
    //@property NSString *firstName;
    //@property NSString *lastName;
    @property NSString *name;
    @end
    RLM_ARRAY_TYPE(UserData)
    

    数据库迁移操作

    - (void)setUp{
        [super setUp];
        RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
        // 叠加版本号(每次更新版本号要比上一次高)
        int newVersion = 5;
        config.schemaVersion = newVersion;
        // 数据迁移
        [config setMigrationBlock:^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
                if (oldSchemaVersion < newVersion) {
                    // 不需要进行操作,自动完成数据结构、数据迁移
                    NSLog(@"需要迁移数据库");
                    [migration enumerateObjects:@"UserData" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
                        newObject[@"name"] = [NSString stringWithFormat:@"%@ %@",oldObject[@"lastName"],oldObject[@"firstName"]];
                    }];
                }
        }];
        // 配置生效
        [RLMRealmConfiguration setDefaultConfiguration:config];
        // 立即迁移
        [RLMRealm defaultRealm];
    }
    - (void)testMigration{
        UserData *user = [UserData new];
        user.userDataId = 4;
        user.name = @"liu new";
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            [realm addObject:user];
        }];
    }
    
    • 高效率修改属性名称(name -> fullname)
    #import <Realm/Realm.h>
    @interface UserData : RLMObject
    @property int userDataId;
    //@property NSString *firstName;
    //@property NSString *lastName;
    //@property NSString *name;
    @property NSString *fullName;
    @end
    RLM_ARRAY_TYPE(UserData)
    
    - (void)setUp{
        [super setUp];
        RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
        // 叠加版本号(每次更新版本号要比上一次高)
        int newVersion = 4;
        config.schemaVersion = newVersion;
        // 数据迁移
        [config setMigrationBlock:^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
                if (oldSchemaVersion < newVersion) {
                    // 不需要进行操作,自动完成数据结构、数据迁移
                    NSLog(@"需要迁移数据库");
                    [migration renamePropertyForClass:@"UserData" oldName:@"name" newName:@"fullName"];
    //                [migration enumerateObjects:@"UserData" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
    //                    newObject[@"name"] = [NSString stringWithFormat:@"%@ %@",oldObject[@"lastName"],oldObject[@"firstName"]];
    //                }];
                }
        }];
        // 配置生效
        [RLMRealmConfiguration setDefaultConfiguration:config];
        // 立即迁移
        [RLMRealm defaultRealm];
    }
    
    • 其他:
      注意版本控制与兼容,可以通过版本判断来区分各个版本修改的数据库代码

    相关文章

      网友评论

        本文标题:iOS开发之realm本地存储

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