前言
由于最近项目中在用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 构建应用的基本要求:
- iOS 7 及其以上版本, macOS 10.9 及其以上版本,此外 Realm 支持 tvOS 和 watchOS 的所有版本。
- 需要使用 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)。更加详细的说明见官方文档。
推荐的安装方法:
- CocoaPods,在项目的Podfile中,添加pod 'Realm',在终端运行pod install。
- 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极为友好,一看就懂,所以这里就按照平时开发的顺序,把需要用到的都梳理一遍。
- 创建数据库
-
一般地,我们使用的为默认的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];
参考链接
网友评论