Realm使用方法及相关特性
最近接触到Realm数据库,经过几天研究感觉Realm就是为速度而生的!在保证了ACID的要求下,很多设计都是以速度为主。当然,Realm 最核心的理念就是对象驱动,这是 Realm 的核心原则。Realm 本质上是一个嵌入式数据库,但是它也是看待数据的另一种方式。它用另一种角度来重新看待移动应用中的模型和业务逻辑。
下面是使用方法以及一些特性
1. 创建数据库
- (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];
}
注:创建数据库主要设置RLMRealmConfiguration,设置数据库名字和存储地方。把路径以及数据库名字拼接好字符串,赋值给fileURL即可。
2. 内存数据库
通常情况下,Realm 数据库是存储在硬盘中的,但是您能够通过设置inMemoryIdentifier而不是设置RLMRealmConfiguration中的 fileURL属性,以创建一个完全在内存中运行的数据库。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];config.inMemoryIdentifier = @"MyInMemoryRealm";RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
内存数据库在每次程序运行期间都不会保存数据。但是,这不会妨碍到 Realm 的其他功能,包括查询、关系以及线程安全。
如果需要一种灵活的数据读写但又不想储存数据的方式的话,那么可以选择用内存数据库。
使用内存数据库需要注意的是:
内存数据库会在临时文件夹中创建多个文件,用来协调处理诸如跨进程通知之类的事务。 实际上没有任何的数据会被写入到这些文件当中,除非操作系统由于内存过满需要清除磁盘上的多余空间。
如果某个内存 Realm 数据库实例没有被引用,那么所有的数据就会被释放。所以必须要在应用的生命周期内保持对Realm内存数据库的强引用,以避免数据丢失。
3. 建表
Realm数据模型是基于标准 Objective‑C 类来进行定义的,使用属性来完成模型的具体定义。
我们只需要继承 RLMObject或者一个已经存在的模型类,您就可以创建一个新的 Realm 数据模型对象。对应在数据库里面就是一张表。
#import <Realm/Realm.h>
@interface RLMUser : RLMObject
@property NSString *accid;
@property NSInteger custId;
@property NSString *custName;
@property NSString *avatarBig;
@property RLMArray<Car> *cars;
RLM_ARRAY_TYPE(RLMUser) // 定义RLMArray<RLMUser>
@interface Car : RLMObject
@property NSString *carName;
@property RLMUser *owner;
@end
RLM_ARRAY_TYPE(Car) // 定义RLMArray<Car>
@end
注意,RLMObject 官方建议不要加上 Objective-C的property attributes(如nonatomic, atomic, strong, copy, weak 等等)假如设置了,这些attributes会一直生效直到RLMObject被写入realm数据库。
4. 存储数据
// (1) 创建一个Car对象,然后设置其属性
Car *car = [[Car alloc] init];
car.carName = @"Lamborghini";
// (2) 通过字典创建Car对象
Car *myOtherCar = [[Car alloc] initWithValue:@{@"name" : @"Rolls-Royce"}];
// (3) 通过数组创建狗狗对象
Car *myThirdcar = [[Car alloc] initWithValue:@[@"BMW"]];
注意,所有的必需属性都必须在对象添加到 Realm 前被赋值
5. 增加操作
[realm beginWriteTransaction];
[realm addObject:Car];
[realm commitWriteTransaction];
请注意,如果在进程中存在多个写入操作的话,那么单个写入操作将会阻塞其余的写入操作,并且还会锁定该操作所在的当前线程。
Realm这个特性与其他持久化解决方案类似,我们建议您使用该方案常规的最佳做法:将写入操作转移到一个独立的线程中执行。
官方给出了一个建议:
由于 Realm 采用了 MVCC 设计架构, 读取操作并不会因为写入事务正在进行而受到影响 。除非您需要立即使用多个线程来同时执行写入操作,不然您应当采用批量化的写入事务,而不是采用多次少量的写入事务。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject: Car];
}];
});
上面的代码就是把写事务放到子线程中去处理。
6. 删除操作
[realm beginWriteTransaction];
// 删除单条记录
[realm deleteObject:Car];
// 删除多条记录
[realm deleteObjects:CarResult];
// 删除所有记录
[realm deleteAllObjects];
[realm commitWriteTransaction];
7. 修改操作
当没有主键的情况下,需要先查询,再修改数据。 当有主键的情况下,有以下几个非常好用的API
[realm addOrUpdateObject:Car];
[Car createOrUpdateInRealm:realm withValue:@{@"id": @1, @"price": @9000.0f}];
addOrUpdateObject会去先查找有没有传入的Car相同的主键,如果有,就更新该条数据。这里需要注意, addOrUpdateObject这个方法不是增量更新 ,所有的值都必须有,如果有哪几个值是null,那么就会覆盖原来已经有的值,这样就会出现数据丢失的问题。
createOrUpdateInRealm:withValue:这个方法是增量更新的,后面传一个字典,使用这个方法的前提是有主键。方法会先去主键里面找有没有字典里面传入的主键的记录,如果有,就只更新字典里面的子集。如果没有,就新建一条记录。
8. 查询操作
在Realm中所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。
查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。同样地,您可以直接通过包含在RLMResults 中的RLMObject对象完成遍历关系图的操作。除非查询结果被使用,否则检索的执行将会被推迟。这意味着链接几个不同的临时 {RLMResults } 来进行排序和匹配数据,不会执行额外的工作,例如处理中间状态。 一旦检索执行之后,或者通知模块被添加之后, RLMResults将随时保持更新,接收 Realm 中,在后台线程上执行的检索操作中可能所做的更改。
//从默认数据库查询所有的车
RLMResults<Car *> *cars = [Car allObjects];
// 使用断言字符串查询
RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = '棕黄色' AND name BEGINSWITH '大'"];
// 使用 NSPredicate 查询
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",@"棕黄色", @"大"];
RLMResults *results = [Dog objectsWithPredicate:pred];
// 排序名字以“大”开头的棕黄色狗狗
RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = '棕黄色' AND name BEGINSWITH '大'"] sortedResultsUsingProperty:@"name" ascending:YES];
Realm链式查询
Realm 查询引擎一个特性就是它能够通过非常小的事务开销来执行链式查询(chain queries),而不需要像传统数据库那样为每个成功的查询创建一个不同的数据库服务器访问。
RLMResults<Car *> *Cars = [Car objectsWhere:@"color = blue"];
RLMResults<Car *> *CarsWithBNames = [Cars objectsWhere:@"name BEGINSWITH 'B'"];
9. Realm支持数据库加密
Realm封装好了加密,如果有隐私数据可以直接加密,目前我这边暂时没有用到加密.
// 产生随机密钥
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
// 打开加密文件
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.encryptionKey = key;
NSError *error = nil;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
// 如果密钥错误,`error` 会提示数据库不可访问
NSLog(@"Error opening realm: %@", error);
}
Realm 支持在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密。这样硬盘上的数据都能都采用AES-256来进行加密和解密,并用 SHA-2 HMAC 来进行验证。每次您要获取一个 Realm 实例时,您都需要提供一次相同的密钥。
不过,加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)。
10. Realm KVC和KVO特性
RLMObject、RLMResult以及 RLMArray
都遵守键值编码(Key-Value Coding)(KVC)机制。当您在运行时才能决定哪个属性需要更新的时候,这个方法是最有用的。 将 KVC 应用在集合当中是大量更新对象的极佳方式,这样就可以不用经常遍历集合,为每个项目创建一个访问器了。
RLMResults<Person *> *persons = [Person allObjects];
[[RLMRealm defaultRealm] transactionWithBlock:^{
[[persons firstObject] setValue:@YES forKeyPath:@"isFirst"]; // 将每个人的 planet 属性设置为“地球”
[persons setValue:@"地球" forKeyPath:@"planet"];
}];
Realm 对象的大多数属性都遵从 KVO 机制。所有 RLMObject子类的持久化(persisted)存储(未被忽略)的属性都是遵循 KVO 机制的,并且 RLMObject以及 RLMArray中 无效的(invalidated)属性也同样遵循(然而 RLMLinkingObjects属性并不能使用 KVO 进行观察)。