文章目录
一 Realm 框架
- 概念介绍
- 开发辅助工具
二 Realm 使用教程
- 1 简单的数据操作
- 创建数据模型
- 使用RLMRealm对象保存指定模型
- 使用RLMRealm对象 更新指定模型
- 使用RLMRealm对象 删除数据
- 查询数据
- 2 支持的数据类型
- 3 模型与模型之间的关系
- 对一关系
- 对多关系
- 反向关系
- 4 可空属性&默认值&忽略属性
- 5 通知
- 6 Realm数据库
- 用户机制
- 只读方式打开数据库
- 数据库文件删除
- 7 数据库迁移
- 数据结构的迁移
- 数据迁移
- 属性重命名
- 多版本增量式迁移
一 Realm 框架
概念介绍:
1 Realm 本质上是一个嵌入式数据库,但是它也是看待数据的另一种方式。它用另一种角度来重新看待你移动应用中的模型和业务逻辑。我们所做的就是尝试减少数据库读写的开销。我们尽可能让其运行得足够快,因此我们一直在调整性能数据。
2 Realm核心数据库引擎打造的,并不是建立在SQLite之上的ORM(对象-关系映射(OBJECT/RELATIONALMAPPING,简称ORM)) 是拥有独立的数据库存储引擎
3 性能: 比SQLite和coreData牛逼(ps:FMDB和coreData 都是基于SQLite封装的)
4 易用 相对于SQLite 和coreData 使用更加简单 更易入门
辅助工具
1 在App Store 搜索 Realm Browser(可视化访问Realm数据库)
2 Xcode 插件 https://github.com/alcatraz/Alcatraz (下载下来后直接编译就会发现xcode中会多了个这个 如下图)
二 realm使用教程:
使用cocoapod集成
pod 'Realm'
1 简单的数据操作
(1) 创建数据模型 继承自RLMObject 也就是上图中的类
创建的模型属性不需要任何属性修饰符 这是因为
由于Realm 在自己的引擎内部有很好的语义解释系统,所以 Objective‑C 的许多属性特性将被忽略,如nonatomic, atomic, strong, copy 和 weak 等。 因此为了避免误解,官方推荐在编写数据模型的时候不要使用任何的属性特性。
请注意,所有的必需属性都必须在对象添加到 Realm 前被赋值
创建对象的方式
// 后边的值 可以出 字典 也可以传数组
// 创建对象 字典
testOneModel *model = [[testOneModel alloc]initWithValue:@{@"num":@2,
@"name":@"奥卡姆剃须刀"
}];
// 数组 后边保存的值 要和模型中的属性 保持一致并且顺序也一致 一一对应
testOneModel *model = [[testOneModel alloc]initWithValue:@[@2,@"奥卡姆剃须刀"]];
(2) 使用RLMRealm对象保存指定模型
// 1 获取RLMRealm对象 可以看做是操作数据库的句柄 操作数据库全靠它
RLMRealm *realm = [RLMRealm defaultRealm];
// 写入方法一
// 开始写入事物
[realm beginWriteTransaction];
// 保存数据库
[realm addObject:model];
// 提交写入事物
[realm commitWriteTransaction];
// 方法二 简洁
// 提供的有block 省去创建Model
[realm transactionWithBlock:^{
[realm addObject:model];
}];
// 方法三 省去了创建Model
[realm transactionWithBlock:^{
[testOneModel createInRealm:realm withValue:@{@"num":@2,
@"name":@"奥卡姆剃须刀"
}];
}];
(3) 使用RLMRealm对象 更新指定模型
// 方式一
// 然后在事物中直接更改
[realm transactionWithBlock:^{
model.num = 27;
}];
// 方式二 直接从realm中取到模型来更新
RLMResults *results = [testOneModel objectsWhere:@"num = 27"];
testOneModel *model = results.firstObject;
[realm transactionWithBlock:^{
model.num = 30;
}];
// 方式三 根据主键进行更新 但是前提是必须在模型中设置好主键 然后此方法会判断该主键是否存在 如果存在就更改 不存在就添加
[realm transactionWithBlock:^{
[realm addOrUpdateObject:model];
}];
// 方式4 和之前的就差不多了 直接在事物中创建模型
[realm transactionWithBlock:^{
[testOneModel createOrUpdateInRealm:realm withValue:@[@10,@"奥卡刀"]];
}];
方式三的配图
设置完成后数据库中该属性也变成主键
(4) 使用RLMRealm对象 删除数据
// 1 删除指定的对象
// 删除的模型一定要求是被realm所管理的 也就是改模型一定是要从realm中取出来的
RLMResults *results = [testOneModel objectsWhere:@"num = 27"];
testOneModel *model = results.firstObject;
[realm transactionWithBlock:^{
[realm deleteObject:model];
}];
// 2 删除指定模型里边的所有数据 通过遍历 只会删除该模型
RLMResults *modelRes = [testOneModel allObjects];
for (testOneModel *model in modelRes) {
[realm transactionWithBlock:^{
[realm deleteObject:model];
}];
}
// 3 删除所有的数据 所有的模型数据都会被清空
[realm transactionWithBlock:^{
// 删除所有的模型
[realm deleteAllObjects];
}];
// 4 根据主键查询到改模型来删除
testOneModel *testModel = [testOneModel objectInRealm:realm forPrimaryKey:@27];
[realm transactionWithBlock:^{
[realm deleteObject:testModel];
}];
(5) 查询数据
// 注意事项: 所有的查询(包括查询和属性访问)在Realm中都是懒加载的 只有当属性被访问时 才能够读取相应的数据
// 查询结棍并不是数据的拷贝(只是数据的映射) 修改查询结果(在写入事务中)会直接修改硬盘上的数据。
//一旦检索执行之后, RLMResults 将随时保持更新
// 1 查询所有数据
RLMResults *modelRes = [testOneModel allObjects];
// 2 条件查询
RLMResults *results = [testOneModel objectsWhere:@"num = 27"];
// 3 查询结果排序
RLMResults *modelRes = [testOneModel allObjects];
// 排序
RLMResults *sortRes = [modelRes sortedResultsUsingKeyPath:@"num" ascending:YES];
// 4 链式查询
// (1) 得到一个查询结果
RLMResults *modelRes2 = [testOneModel objectsWhere:@"num > 1"];
// (2) 在该结果集上继续进行查询 得到一个新的结果集
RLMResults *modelRes3 = [modelRes objectsWhere:@"num < 10"];
// 5 分页 (这个分页我们客户端做的比较少 一般都是后台做处理 )
// 查询出来的结果对象是懒加载 只有真正访问时 才会加载相应对象 所以这里的分页 其实就是从所有集合中分页获取即可
RLMResults *allModels = [testOneModel allObjects];
for (int i = 3; i <= 6; i++) {
testOneModel *model = allModels[i];
}
2 支持的数据类型
支持的类型: BOOL, bool, int, NSInteger, long, long long, float, double, NSString, NSDate, NSData, and NSNumber
// 不支持集合类型
// 解决方案 1 序列化成NSData进行存储 2 转换成RLMArray进行存储
// 例如存储Image
testOneModel *model = [[testOneModel alloc]init];
model.num = 10;
model.name = @"奥卡姆剃须刀";
// realm不支持image 不能这样存错
// model.image = [UIImage imageNamed:@"123.jpg"];
正确的存储方式
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"123.jpg" ofType:nil];
NSData *data = [NSData dataWithContentsOfFile:filePath];
model.imageData = data;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:model];
}];
但是需要忽略到模型中的image
采用readOnly修饰即可
然后重写image的get方法 可以在外界取到改图片
- (UIImage *)image{
return [UIImage imageWithData:self.imageData];
}
包括集合类的也不能直接存储
存储集合 我们需要在创建一个继承自RLMObject的模型
@interface testTwoModel : RLMObject
@property NSString *videoName;
@end
// 这个testTwoModel 是 专门转换数组的 返回来的字符串类边先转换成该模型
// 这个集合有个要求 里边存储的属性必须是继承自RLMObject类型的
@property RLMArray *arrays;
3 模型与模型之间的关系
(1) 对一关系
当一个对象持有另外一个对象时, 比如人有一辆车
@interface Person : RLMObject
@property int num;
@property NSString *name;
// 对一关系
@property Car *car;
(2) 对多关系
当一个对象持有另外一个对象时, 人有车 但是 不是只有一辆 是有很多辆车 是一个集合
有两个规则是要遵守的
- 在Dog中, 遵循指定协议方法
RLM_ARRAY_TYPE(Dog)
RLM_ARRAY_TYPE
宏创建了一个协议,从而允许RLMArray
语法的使用。
2 在Person中, 定义属性
@property (nonatomic, strong) RLMArray *dogs;
注意 虽然可以给 RLMArray 属性赋值为 nil,但是这仅用于“清空”数组,而不是用以移除数组。这意味着您总是可以向一个 RLMArray 属性中添加对象,即使其被置为了 nil。
Person *p = [[Person alloc]init];
p.num = 10;
p.name = @"剃须刀";
Car *car1 = [Car new];
car1.name = @"Smart";
Car *car2 = [Car new];
car2.name = @"BMW";
// 初始化的集合为nil 但是其内部已经帮我们做好了 我们可以这样直接添加
[p.cars addObject:car1];
[p.cars addObject:car2];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:p];
}];
(3) 反向关系
人拥有汽车 汽车又拥有人
1 在car.h中定义属性
@property (readonly)RLMLinkingObjects *master;
2 在car.m 中实现协议方法 表明链接关系 即可
+ (NSDictionary *)linkingObjectsProperties{
return @{
@"master":[RLMPropertyDescriptor descriptorWithClass:NSClassFromString(@"Person") propertyName:@"cars"]
};
}
外界不需要单独给car的属性master复制 就可以得到master
如下
Person *p = [Person allObjects].firstObject;
NSLog(@"%@",p.cars.firstObject.master);
4 可空属性&默认值&忽略属性
(1) 默认情况下 属性值可以为空 如果强制要求每个属性非空,可以使用如下方法
//在.m 中实现这个方法 如果在此复制为nil 则会抛出异常
// 限制此属性不能为空
+ (NSArray *)requiredProperties{
return @[@"name"];
}
(2)给属性设置默认值
// 设置指定属性的默认值
+ (NSDictionary *)defaultPropertyValues{
return @{@"name":@"黑娃"};
}
(3)忽略属性不存储
//1 使用readonly修饰 就不会存储改属性
@property (readonly)NSString *time;
// 2 也可以实现下边的这个方法 也不会存储该数组中的属性
+ (NSArray *)ignoredProperties
{
return @[@"time"];
}
可以借助这几个 忽略属性 和只读属性 打造计算属性 完成集合 以及UIImage 对象的存储
5 通知
一般用于 realm 存储数据成功后 发送通知 刷新列表
// 必须要将token有强引用
@property(nonatomic, strong)RLMNotificationToken *token;
// 初始化
- (void)setUp {
[super setUp];
RLMRealm *realm = [RLMRealm defaultRealm];
// 1 第一种方式添加通知 必须要将token有强引用
self.token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) {
// 接收到通知后 可做刷新
}];
}
// 第二种方式添加通知
// 可以具体拿到更改了什么数据
self.token2 = [results addNotificationBlock:^(RLMResults * _Nullable results, RLMCollectionChange * _Nullable change, NSError * _Nullable error) {
NSLog(@"%@---%@---%@",results,change,error);
}];
// 2 存储成功后 会默认调用通知的方法
NoticeModel *model = [[NoticeModel alloc]initWithValue:@[@10]];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:model];
}];
// 释放
- (void)tearDown {
// 3 停止token 相当去移除通知
[self.token stop];
[super tearDown];
}
6 Realm数据库
(1)用户机制
在我们实际开发中肯定会有很多的用户 所以不同的用户 。肯定要使用不同的数据库文件
// 调用此方法 配置数据库的名字
[self setDefailtRealmForUser:@"tixvdao"];
// 然后存储的数据 都会在此数据库中
RLMRealm *realm = [RLMRealm defaultRealm];
Data *data = [Data new];
data.a = 10;
[realm transactionWithBlock:^{
[realm addObject:data];
}];
// 配置数据的名字
- (void)setDefaultRealmForUser:(NSString *)username {
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 使用默认的目录,但是使用用户名来替换默认的文件名
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
URLByAppendingPathComponent:username]
URLByAppendingPathExtension:@"realm"];
// 将这个配置应用到默认的 Realm 数据库当中
[RLMRealmConfiguration setDefaultConfiguration:config];
(2) 只读方式打开数据库
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 以只读模式打开文件 因为应用数据包并不可写
config.readOnly = YES;
[RLMRealmConfiguration setDefaultConfiguration:config];
RLMRealm *realm = [RLMRealm defaultRealm];
// 设置了只读属性后 就不能再添加或修改数据了 此步骤会报错
Data *data = [Data new];
data.a = 12;
[realm transactionWithBlock:^{
[realm addObject:data];
}];
(3) 数据库文件删除
注意 需要删除数据库文件以及辅助文件
//1 设置默认数据库的路径
[self setDefailtRealmForUser:@"tixvdao"];
NSFileManager *manager = [NSFileManager defaultManager];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
NSArray * 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];
}
7 数据库迁移
适用于修改了数据模型的情况
(1) 数据结构的迁移
// 正常的应该吧此数据库迁移放在appdelegate中 程序一启动就要做数据库的迁移
// 1 获取默认配置
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 2 设置新的架构版本 这个版本高于之前的所用的版本 如果之前没有设置版本 那版本号就是0
int nerVersion = 1;
config.schemaVersion = nerVersion;
// 3 具体迁移 设置闭包 这个闭包将会在打开低于上边的版本号的realm 数据库的时候被自动调用
config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
// 目前我们还未进行数据迁移 因此oldSchemaVersion == 0
if (oldSchemaVersion < nerVersion) {
// 这里边什么都不需要做 realm会自行检测新增和需要移除的属性 然后自动更新硬盘上的数据库架构
}
};
// 4 告诉realm 为默认的realm 数据库使用这个新的配置对象
[RLMRealmConfiguration setDefaultConfiguration:config];
// 现在我们已经告诉 了 realm 如何处理架构的变化 打开文件之后将会自动执行迁移
// 如果需要立即迁移 只要访问数据后就会直接执行
[RLMRealm defaultRealm];
(2) 数据迁移
// 正常的应该吧此数据库迁移放在appdelegate中 程序一启动就要做数据库的迁移
// 1 获取默认配置
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 2 设置新的架构版本 这个版本高于之前的所用的版本 如果之前没有设置版本 那版本号就是0
// 一定要记得每一次版本号叠加
int nerVersion = 2;
config.schemaVersion = nerVersion;
// 3 具体迁移 设置闭包 这个闭包将会在打开低于上边的版本号的realm 数据库的时候被自动调用
config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
// 目前我们还未进行数据迁移 因此oldSchemaVersion == 0
if (oldSchemaVersion < nerVersion) {
// (1) 数据结构迁移的话 这里什么都不需要做
// 这里边什么都不需要做 realm会自行检测新增和需要移除的属性 然后自动更新硬盘上的数据库架构
// (2) 数据迁移的话 可以把原来的 值 合并到新的值
// enumerateObjects:block: 方法遍历了存储在 Realm 文件中的每一个“Migretion”对象
[migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
newObject[@"fullName"] = [NSString stringWithFormat:@"%@%@",oldObject[@"preName"],oldObject[@"lastName"]];
}];
}
};
// 4 告诉realm 为默认的realm 数据库使用这个新的配置对象
[RLMRealmConfiguration setDefaultConfiguration:config];
// 现在我们已经告诉 了 realm 如何处理架构的变化 打开文件之后将会自动执行迁移
// 如果需要立即迁移 只要访问数据后就会直接执行
[RLMRealm defaultRealm];
// 1 preName和 lastName 字段都已经没有了 将之前的数据全部合并到新的fullName字段
Migretion *model = [[Migretion alloc]init];
//
// model.preName = @"aokamu";
// model.lastName = @"tixvdao";
model.fullName = @"tixvdao";
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:model];
}];
(3) 属性重命名
// 3 更高属性名 这中方法性能不好
// [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
// newObject[@"fullTitle"] = oldObject[@"fullName"];
// }];
// 更名属性 特有的额方法
[migration renamePropertyForClass:@"Migretion" oldName:@"fullName" newName:@"fullTitle"];
(4) 多版本增量式迁移
多版本增量式迁移的意思就是 版本升级 会有不同的情况
V0 -> V1
V1 -> V2
V0 -> V2
//V0
@property int age;
@property NSString *preName;
@property NSString *lastName;
//V1
@property int age;
@property NSString *fullName;
// V2
@property int age;
@property NSString *fullTitle;
// 迁移核心代码
// 3 具体迁移 设置闭包 这个闭包将会在打开低于上边的版本号的realm 数据库的时候被自动调用
config.migrationBlock = ^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
// 目前我们还未进行数据迁移 因此oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// (1) 数据结构迁移的话 这里什么都不需要做
// 这里边什么都不需要做 realm会自行检测新增和需要移除的属性 然后自动更新硬盘上的数据库架构
}
if (oldSchemaVersion < 2) {
// (2) 数据迁移的话 可以把原来的 值 合并到新的值
// enumerateObjects:block: 方法遍历了存储在 Realm 文件中的每一个“Migretion”对象
// [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
// newObject[@"fullName"] = [NSString stringWithFormat:@"%@%@",oldObject[@"preName"],oldObject[@"lastName"]];
// }];
}
if (oldSchemaVersion < 3) {
// 3 更高属性名 这中方法性能不好
// [migration enumerateObjects:@"Migretion" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) {
// newObject[@"fullTitle"] = oldObject[@"fullName"];
// }];
// 更名属性 特有的额方法
[migration renamePropertyForClass:@"Migretion" oldName:@"fullName" newName:@"fullTitle"];
}
};
《经过多次更新终于完结...》