Realm框架
介绍:
-
realm是一个跨平台移动数据库引擎,支持iOS、OS X(Objective-C和Swift)以及Android
-
核心数据引擎C++打造,并不是建立在SQLite之上的ORM, 是拥有独立的数据库存储引擎 - realm.io/cn/news/jp-…
-
注解:ORM
- 对象-关系映射(OBJECT/RELATIONALMAPPING,简称ORM)
- ORM技术是在对象和关系之间提供了一条桥梁
-
性能 比sqlite, coredata牛逼
-
易用 相比于sqlite, coredata, 使用起来更加简单, 更易入门
-
辅助工具
- Realm Browser 可视化访问 Realm 数据库
- AppStore 已经不更新维护了 去这个地址下载 docs.realm.io/sync/realm-…
- Xcode插件 github.com/alcatraz/Al… 可以快速创建Realm
点击 release.zip 下载插件。 可存储模型对象
Realm实战
1. 简单的数据操作
- 1.1准备: 创建数据模型, 继承自RLMObject必须
- 创建对象的方式
- 普通创建
- 通过父类RLMObject中的方法快速创建
initWithValue
+字典 键值对
+数组 跟属性顺序保持一致 - 注意
- 请注意,所有的必需属性都必须在对象添加到 Realm 前被赋值
- 由于Realm 在自己的引擎内部有很好的语义解释系统,所以 Objective‑C 的许多属性特性将被忽略,如nonatomic, atomic, strong, copy 和 weak 等。 因此为了避免误解,官方推荐在编写数据模型的时候不要使用任何的属性特性。
- 1.2 使用RLMRealm对象, 保存指定模型
- 获取RLMRealm对象 RLMRealm *realm = [RLMRealm defaultRealm];
- 写入方式1
开启写入事务
[realm beginWriteTransaction];
添加模型对象[realm addObject:stu];
提交写入事务[realm commitWriteTransaction];
- 写入方式2
[realm transactionWithBlock:^{[realm addObject:stu];}];
- 写入方式3
[Stu createInRealm:realm withValue:@{@"stu_id": @22, @"name": @"马冬梅2", @"age": @666}];
- 1.3 使用RLMRealm对象, 更新指定模型
- 方式1 在事务中直接更新对象
[realm beginWriteTransaction]; stu.name = @"土豆"; [realm commitWriteTransaction];
- 方式2 根据主键进行更新
-
- 要求操作的模型, 必须实现方法+ (NSString *)primaryKey 返回主键
-
- 在事务中调用方法 [realm addOrUpdateObject:stu2];
-
- 方式3 根据主键进行更新
-
- 要求操作的模型, 必须实现方法+ (NSString *)primaryKey 返回主键
-
- 在事务中调用方法
[Stu createInRealm:realm withValue:@{@"stu_id": @22, @"name": @"马冬梅2", @"age": @666}];
- 在事务中调用方法
-
- 1.4 使用RLMRealm对象, 删除数据
-
删除指定的对象
- 在事务中
[realm deleteObject:stu];
- 注意: 必须是从realm数据库中获取的模型对象, 而不是自己创建的
RLMObject *obj = [realm objectWithClassName:@"Stu" forPrimaryKey:@2];
- 在事务中
-
删除所有对象 在事务中
[realm deleteAllObjects];
-
- 1.5 使用RLMRealm对象, 查询数据
- 注意事项
-
- 所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据
-
- 查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。
-
- 一旦检索执行之后, RLMResults 将随时保持更新
-
- 查询所有
[Stu allObjects]
- 条件查询
RLMResults<Stu *> *stus = [Stu objectsWhere:@"name = '马冬梅'"];
- 排序
[stus sortedResultsUsingProperty:@"name" ascending:YES];
- 链式查询
含义 在查询结果的基础上, 进行二次查询
[stus objectsWhere:@"address beginswith '北京'"];
- 分页
- 注意:
- 查询出来的结果对象是懒加载, 只有真正访问时, 才会加载相应对象, 所以, 这里的分页, 其实就是从所有集合中分页获取即可
- 代码演示
RLMResults<Dog *> *dogs = [Dog allObjects]; for (NSInteger i = 0; i < 5; i++) { Dog *dog = dogs[i]; // ... }
- 注意:
- 注意事项
- 2.支持的数据类型
BOOL, bool, int, NSInteger, long, long long, float, double, NSString, NSDate, NSData, and NSNumber
- 注意:不支持集合类型
- 解决方案
- 序列化成NSData进行存储
- 转换成RLMArray进行存储
- 实战案例 存储image
- 3.关系
- 对一关系 : 当一个对象持有另外一个对象时, 比如人有一个宠物🐶
- 对多关系
-
- 在Dog中, 遵循指定协议方法
- RLM_ARRAY_TYPE(Dog)
- RLM_ARRAY_TYPE 宏创建了一个协议,从而允许 RLMArray 语法的使用。
-
- 在Person中, 定义属性
- @property (nonatomic, strong) RLMArray<Dog *> *dogs;
- 注意:虽然可以给 RLMArray 属性赋值为 nil,但是这仅用于“清空”数组,而不是用以移除数组。这意味着您总是可以向一个 RLMArray 属性中添加对象,即使其被置为了 nil。
-
- 反向关系
- 举例 :人拥有狗, 狗又有相应的主人?
- 解决
-
- Dog中定义属性 @property (readonly) RLMLinkingObjects *master;
-
- 实现协议方法, 标明链接关系
+ (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties { return @{ @"master": [RLMPropertyDescriptor descriptorWithClass:NSClassFromString(@"Stu") propertyName:@"dogs"] }; }
-
-
- 可空属性&默认值&忽略属性
- 默认情况下, 属性值可空, 如果强制要求某个属性非空, 可以使用如下方法
- 遵循协议方法
+ (NSArray *)requiredProperties { return @[@"name"]; }
- 特点 如果再次赋值为nil, 则会抛出异常错误
- 也可以设置默认值
+ (NSDictionary *)defaultPropertyValues { return @{@"name": @""}; }
- 忽略属性
- 不想存储的某些属性
- 实现方法
+ (NSArray *)ignoredProperties
- 开发经验 可以借助忽略属性&只读属性 打造计算属性, 完成集合以及UIImage对象的存储与获取
-
- 通知
- Realm 实例将会在每次写入事务提交后,给其他线程上的 Realm 实例发送通知
-
- 获取 Realm 通知
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) { // 接收到更改通知, 需要做的事情 }];
-
- 移除通知 [token stop]; 注意: 必须持有返回的token
-
- Realm数据库
- 用户机制
- 不同的用户, 使用不同的数据库文件
- 实现方案
-
- 不同的用户, 使用不同的数据库
-
+ (void)setDefaultRealmForUser:(NSString *)username {
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 使用默认的目录,但是使用用户名来替换默认的文件名
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]URLByAppendingPathComponent:username]URLByAppendingPathExtension:@"realm"];
// 将这个配置应用到默认的 Realm 数据库当中
RLMRealmConfiguration setDefaultConfiguration:config];
}
- 只读方式打开数据库
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 获取需要打包文件的 URL 路径
config.fileURL = [[NSBundle mainBundle] URLForResource:@"MyBundledData" withExtension:@"realm"];
// 以只读模式打开文件,因为应用数据包并不可写
config.readOnly = YES;
// 通过配置打开 Realm 数据库
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
// 从打包的 Realm 数据库中读取某些数据
RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"age > 5"];
- 数据库文件删除
注意:1. 需要删除数据库文件以及辅助文件
代码实战
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) {
// 处理错误
}
}
-
- 数据库迁移
- 适用于修改了数据模型的情况
- 数据结构迁移
// 在 [AppDelegate didFinishLaunchingWithOptions:] 中进行配置 RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // 设置新的架构版本。这个版本号必须高于之前所用的版本号(如果您之前从未设置过架构版本,那么这个版本号设置为 0) config.schemaVersion = 1; // 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用 config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { // 目前我们还未进行数据迁移,因此 oldSchemaVersion == 0 if (oldSchemaVersion < 1) { // 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构 } }; // 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象 [RLMRealmConfiguration setDefaultConfiguration:config]; // 现在我们已经告诉了 Realm 如何处理架构的变化,打开文件之后将会自动执行迁移 [RLMRealm defaultRealm];
- 数据迁移
// enumerateObjects:block: 方法遍历了存储在 Realm 文件中的每一个“Person”对象 [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // 将名字进行合并,存放在 fullName 域中 newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",oldObject[@"firstName"],oldObject[@"lastName"]]; }];
- 属性重命名
[migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];
- 多版本增量式迁移
- 场景
- 版本0
// v0
// @interface Person : RLMObject
// @property NSString *firstName;
// @property NSString *lastName;
// @property int age;
@end
- 版本1
// v1
// @interface Person : RLMObject
// @property NSString *fullName; // 新属性
// @property int age;
// @end
- 版本2
// v2
@interface Person : RLMObject
@property NSString *fullName;
@property NSString *email; // 新属性
@property int age;
@end
- 迁移核心代码
// enumerateObjects:block: 遍历了存储在 Realm 文件中的每一个“Person”对象
[migration enumerateObjects:Person.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"] = @"";
}
}];