Realm的简单使用

字数 2419阅读 1449

前言

由于最近项目中在用Realm,所以把自己实践过程中的一些心得总结分享一下。

Realm是由Y Combinator孵化的创业团队开源出来的一款可以用于iOS(同样适用于Swift&Objective-C)和Android的跨平台移动数据库。目前最新版是Realm 2.0.2,支持的平台包括Java,Objective-C,Swift,React Native,Xamarin。

Realm官网上说了好多优点,我觉得选用Realm的最吸引人的优点就三点:

跨平台:现在很多应用都是要兼顾iOS和Android两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用Realm提供的API,可以使数据持久化层在两个平台上无差异化的转换。

简单易用:Core Data 和 SQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用 Realm,则可以极大地减少学习成本,立即学会本地化存储的方法。毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了。

可视化:Realm 还提供了一个轻量级的数据库查看工具,在Mac Appstore 可以下载“Realm Browser”这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。毕竟,很多时候,开发者使用数据库的理由是因为要提供一些所谓的“知识库”。

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文件即可.

自2012年起, Realm 就已经开始被用于正式的商业产品中了。经过4年的使用,逐步趋于稳定。

Realm 安装

使用 Realm 构建应用的基本要求:

  1. iOS 7 及其以上版本, macOS 10.9 及其以上版本,此外 Realm 支持 tvOS 和 watchOS 的所有版本。
  2. 需要使用 Xcode 7.3 或者以后的版本。

注意: 这里如果是纯的OC项目,就安装OC的Realm,如果是纯的Swift项目,就安装Swift的Realm。如果是混编项目,就需要安装OC的Realm,然后要把 Swift/RLMSupport.swift 文件一同编译进去。

RLMSupport.swift这个文件为 Objective-C 版本的 Realm 集合类型中引入了 Sequence 一致性,并且重新暴露了一些不能够从 Swift 中进行原生访问的 Objective-C 方法,例如可变参数 (variadic arguments)。更加详细的说明见官方文档

推荐的安装方法:

  1. CocoaPods,在项目的Podfile中,添加pod 'Realm',在终端运行pod install。
  2. Static Framework
  • 下载 Realm 的最新版本并解压,将 Realm.framework 从 ios/static/文件夹拖曳到您 Xcode 项目中的文件导航器当中。确保 Copy items if needed 选中然后单击 Finish;
  • 在 Xcode 文件导航器中选择您的项目,然后选择您的应用目标,进入到 Build Phases 选项卡中。在 Link Binary with Libraries 中单击 + 号然后添加libc++.dylib;

Realm 中的相关术语

为了能更好的理解Realm的使用,先介绍一下涉及到的相关术语。

RLMRealm:Realm是框架的核心所在,是我们构建数据库的访问点,就如同Core Data的管理对象上下文(managed object context)一样。出于简单起见,realm提供了一个默认的defaultRealm( )的便利构造器方法。

RLMObject:这是我们自定义的Realm数据模型。创建数据模型的行为对应的就是数据库的结构。要创建一个数据模型,我们只需要继承RLMObject,然后设计我们想要存储的属性即可。

关系(Relationships):通过简单地在数据模型中声明一个RLMObject类型的属性,我们就可以创建一个“一对多”的对象关系。同样地,我们还可以创建“多对一”和“多对多”的关系。

写操作事务(Write Transactions):数据库中的所有操作,比如创建、编辑,或者删除对象,都必须在事务中完成。“事务”是指位于write闭包内的代码段。

查询(Queries):要在数据库中检索信息,我们需要用到“检索”操作。检索最简单的形式是对Realm( )数据库发送查询消息。如果需要检索更复杂的数据,那么还可以使用断言(predicates)、复合查询以及结果排序等等操作。

RLMResults:这个类是执行任何查询请求后所返回的类,其中包含了一系列的RLMObject对象。RLMResults和NSArray类似,我们可以用下标语法来对其进行访问,并且还可以决定它们之间的关系。不仅如此,它还拥有许多更强大的功能,包括排序、查找等等操作。

Realm如何使用

由于Realm的API极为友好,一看就懂,所以这里就按照平时开发的顺序,把需要用到的都梳理一遍。

  1. 创建数据库
  • 一般地,我们使用的为默认的Realm数据库,即调用[RLMRealm defaultRealm]来初始化以及访问我们的realm变量。这个方法将会返回一个 RLMRealm对象,并指向您应用的 Documents (iOS) 文件夹下的一个名为“default.realm”的文件。

  • 用自己创建的数据库

-(void)creatDataBaseWithName:(NSString *)databaseName {
    NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [docPath objectAtIndex:0];
    NSString *filePath = [path stringByAppendingPathComponent:databaseName];
    NSLog(@"数据库目录 = %@",filePath);
    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    config.fileURL = [NSURL URLWithString:filePath];
    config.objectClasses = @[MyClass.class, MyOtherClass.class];
    config.readOnly = NO;
    int currentVersion = 1.0;
    config.schemaVersion = currentVersion;
    config.migrationBlock = ^(RLMMigration *migration , uint64_t oldSchemaVersion) {
       // 这里是设置数据迁移的block
        if (oldSchemaVersion < currentVersion) {
        }
    };
    [RLMRealmConfiguration setDefaultConfiguration:config];
}

拓展:

// 查询指定的 Realm 数据库
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 获得一个指定的 Realm 数据库
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 从该 Realm 数据库中,检索所有狗狗

创建数据库主要设置RLMRealmConfiguration,设置数据库名字和存储地方。把路径以及数据库名字拼接好字符串,赋值给fileURL即可。

objectClasses这个属性是用来控制对哪个类能够存储在指定 Realm 数据库中做出限制。例如,如果有两个团队分别负责开发您应用中的不同部分,并且同时在应用内部使用了 Realm 数据库,那么您肯定不希望为它们协调进行数据迁移您可以通过设置RLMRealmConfiguration的 objectClasses属性来对类做出限制。objectClasses一般可以不用设置。
readOnly是控制是否只读属性。

2.建表

  • 创建简单数据模型
#import <Realm/Realm.h>

@interface MJCoutryModel : RLMObject

@property (nonatomic, copy) NSString *countryId;

@property (nonatomic, copy) NSString *country;

@property (nonatomic, copy) NSString *dialCode;

@end

RLM_ARRAY_TYPE(MJCoutryModel)

可以设置配置

//主键
+ (NSString *)primaryKey {
    return @"countryId";
}

////设置属性默认值
//+ (NSDictionary *)defaultPropertyValues {
//    return @{@"dialCode":@"00" };
//}

//设置忽略属性,即不存到realm数据库中
+ (NSArray<NSString *> *)ignoredProperties {
    return @[@"country"];
}

//一般来说,属性为nil的话realm会抛出异常,但是如果实现了这个方法的话,就只有countryId为nil会抛出异常,也就是说现在dialCode属性可以为空了
+ (NSArray *)requiredProperties {
    return @[@"countryId"];
}

//设置索引,可以加快检索的速度
+ (NSArray *)indexedProperties {
    return @[@"countryId"];
}
  • 创建嵌套数据模型
#import <Realm/Realm.h>
@class Person;
// 狗狗的数据模型
@interface Dog : RLMObject
@property NSString *name;
@property Person   *owner;
@end
RLM_ARRAY_TYPE(Dog) // 定义RLMArray<Dog>
// 狗狗主人的数据模型
@interface Person : RLMObject
@property NSString      *name;
@property NSDate        *birthdate;
// 通过RLMArray建立关系
@property RLMArray<Dog> *dogs;
@end
RLM_ARRAY_TYPE(Person) // 定义RLMArray<Person>

注意:RLMObject 官方建议不要加上 Objective-C的property attributes(如nonatomic, atomic, strong, copy, weak 等等)假如设置了,这些attributes会一直生效直到RLMObject被写入realm数据库。

3.增

    Dog *myDog = [[Dog alloc] init];
    myDog.name = @"小花";
    
    Dog *yourDog = [[Dog alloc] init];
    yourDog.name = @"小黑";
    
    Person *me = [[Person alloc] initWithValue:@[@"小明",[NSDate dateWithTimeIntervalSinceNow:1],@[myDog,yourDog]]];
    
    yourDog.owner = me;
    myDog.owner = me;
    
    
    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm beginWriteTransaction];
    [realm addObject:yourDog];
    [realm addObject:myDog];
    [realm addObject:me];
    [realm commitWriteTransaction];

使用Realm进行数据管理的方式:
方式一:

 RLMRealm *realm = [RLMRealm defaultRealm];

// 开放RLMRealm事务
[realm beginWriteTransaction];

// 在开放开放/提交事务之间进行数据处理

// 提交事务
[realm commitWriteTransaction];

方式二:

[realm transactionWithBlock:^{

 // 进行数据处理

}];

4.删

清空所有数据

   
    RLMRealm *realm = [RLMRealm defaultRealm];
    RLMResults<Person *> *personResults = [Person allObjects];
    if (personResults.count > 0) {
        [realm beginWriteTransaction];
        [realm deleteObjects:personResults];
        [realm commitWriteTransaction];
    }
    
    RLMResults<Dog *> *dogResults = [Dog allObjects];
        [realm beginWriteTransaction];
        [realm deleteAllObjects];
        [realm commitWriteTransaction];

清空某条数据

   
    RLMRealm *realm = [RLMRealm defaultRealm];
    RLMResults<MJCoutryModel *> *walletResults = [MJCoutryModel allObjects];
    MJCoutryModel *wallet1 = [walletResults firstObject];

    [realm beginWriteTransaction];
    [realm deleteObject:wallet1];
    [realm commitWriteTransaction];

5.查
数据库查询

// 查询默认的 Realm 数据库
RLMResults *dogs = [Dog allObjects]; // 从默认的 Realm 数据库中,检索所有狗狗

如果有需要,也可以查询指定的数据库

// 查询指定的 Realm 数据库
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 获得一个指定的 Realm 数据库
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 从该 Realm 数据库中,检索所有狗狗

条件查询
1.使用断言字符串查询:

RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黄色' AND name BEGINSWITH '大'" ascending:YES];

2.使用 NSPredicate 查询

NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
                                                 @"棕黄色", @"大"];
RLMResults *tanDogs = [Dog objectsWithPredicate:pred];

3.链式查询
如果我们想获得获得棕黄色狗狗的查询结果,并且在这个查询结果的基础上再获得名字以“大”开头的棕黄色狗狗。

RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黄色'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH '大'"];

6.改

  • 当没有主键的情况下,需要先查询,再修改数据。
   // 先查询
    RLMRealm *realm = [RLMRealm defaultRealm];
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"dialCode = %@ AND country BEGINSWITH %@",@"123", @"中国"];
    RLMResults<MJCoutryModel *> *walletResults = [MJCoutryModel objectsWithPredicate:pred];
   // 再修改
    for (int i = 0; i < walletResults.count; i++) {
        MJCoutryModel *wallet = walletResults[i];
        [realm beginWriteTransaction];
        wallet.country = @"托马斯品钦";
        [realm commitWriteTransaction];
    }
  • 当有主键的情况下,有以下几个非常好用的API
    RLMRealm *realm = [RLMRealm defaultRealm];
    
    MJCoutryModel *country = [[MJCoutryModel alloc] init];
    country.countryId = @"21";
    country.country = @"ee21";
    country.dialCode = @"22";
    
    [realm beginWriteTransaction];
    // 方法一
    [MJCoutryModel createOrUpdateInRealm:realm withValue:country];
    // 方法二
    [realm addOrUpdateObject:country];
    
    [realm commitWriteTransaction];

addOrUpdateObject: 会去先查找有没有传入的Car相同的主键,如果有,就更新该条数据。这里需要注意,addOrUpdateObject这个方法不是增量更新,所有的值都必须有,如果有哪几个值是null,那么就会覆盖原来已经有的值,这样就会出现数据丢失的问题。

createOrUpdateInRealm:withValue:这个方法是增量更新的,后面传一个字典,使用这个方法的前提是有主键。方法会先去主键里面找有没有字典里面传入的主键的记录,如果有,就只更新字典里面的子集。如果没有,就新建一条记录。

数据迁移

当您使用任意一个数据库时,您随时都可能打算修改您的数据模型。通过设置 RLMRealmConfiguration.schemaVersion 以及RLMRealmConfiguration.migrationBlock 可以定义一个迁移操作以及与之关联的架构版本。 迁移闭包将会提供提供相应的逻辑操作,以让数据模型从之前的架构转换到新的架构中来。 每当通过配置创建完一个 RLMRealm 之后,迁移闭包将会在迁移需要的时候,将给定的架构版本应用到更新 RLMRealm 操作中。
如下所示是最简单的数据迁移的必需流程:

// 在 [AppDelegate didFinishLaunchingWithOptions:] 中进行配置

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 2;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion)
{
    // enumerateObjects:block: 遍历了存储在 Realm 文件中的每一个“Person”对象
    [migration enumerateObjects:MJCoutryModel.className block:^(RLMObject *oldObject, RLMObject *newObject) {
        // 只有当 Realm 数据库的架构版本为 0 的时候,才添加 “fullName” 属性
        if (oldSchemaVersion < 1) {
            newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]];
        }
        // 只有当 Realm 数据库的架构版本为 0 或者 1 的时候,才添加“email”属性
        if (oldSchemaVersion < 2) {
            newObject[@"email"] = @"";
        }
       // 替换属性名
       if (oldSchemaVersion < 3) { // 重命名操作应该在调用 `enumerateObjects:` 之外完成 
            [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"]; }
    }];
};
[RLMRealmConfiguration setDefaultConfiguration:config];
// 现在我们已经成功更新了架构版本并且提供了迁移闭包,打开旧有的 Realm 数据库会自动执行此数据迁移,然后成功进行访问
[RLMRealm defaultRealm];

参考链接

Realm官网
Realm官方文档
Realm GitHub
参考博客一
参考博客二

推荐阅读更多精彩内容