关于Realm的使用
其实这个是我在接手项目之后看到的,我也不知道它是什么,就百度了一下,然后考虑到后续如果有商城需求,有收藏的需求,可能就会用到数据库,根据sqlite封装的FMDB很久没用了略显生疏,plist存储又有点不机动,CoreData还不太知道用法,NSUserDefault又显得不好用,所以既然这么巧,那我就考虑用下这个
导包的话,由于我是在他基础上做的开发,他用的realm很老很老了,以至于如图示的软件都提示要更新了
realm database.png
而且很重要一点,要想查看数据库的东西,一定要把程序关掉,反之,如果运行程序,也要关掉数据库,不然运行之后会显示数据库在运行(可能显示别的,总之显示一个异常),也会报错
不更新没发现什么,一更新数据库之后就发现程序崩溃了
Unsupported Realm file format version
也就是数据库已经更新了,不支持这个版本了,想再使用就难了,所以如果用的老版本的数据库,请一定一定,一定不要用这个软件打开了,如果拖入到realm数据库的话,会提示更新的问题,它会把realm数据库数据更新到很高版本,此时就会报这个错,会提示用新版本,代码本身是没有问题的
设计数据库的字段,我这里就放代码吧,在代码里进行注解,来了解下
Stu.h
// 导包
#import <Realm/Realm.h>
#import <UIKit/UIKit.h>
// 导入一个imgData类
#import "ImgData.h"
//继承RLMObject
@interface Stu : RLMObject
//对象的属性声明,注意请不要带上nonatomic,assign,strong等这样修饰的词(官方推荐避免引起奇葩错误),readonly可以但是在realm里面有新的作用,后面讲.
// 而且要注意顺序,[[Stu alloc] initWithValue:@[@"zhangsan",@26]];这个方法,就是按照模型顺序弄的
@property NSString *name;
@property int num;
// 由于image跟sqlite数据库一样,不能直接进行存储,所以只能通过NSData来进行缓冲,但是由于记录在数据中,它还是会读出来,报错,所以采取(readonly),设置只读字段来拦截
// 只读属性会被自动忽略
@property (readonly)UIImage *image;
// 没传值时为:<(null) — 0 total bytes>;这个表示为可选值,也就是,可以传空
@property NSData *imageData;
// 定义数组变量 这个集合有个要求,这里面存储的属性,必须是继承自RLMObject类型的属性,由于是如此,比方说要存一个student对象的话所以要这么写:
// RLMArray<student *><student> *student;
// 针对如果说数据库要保存一组图片的方法,先在某处定义一个如此的模型,然后再定义一个专属的数组,包装一下,然后在.m方法里一个一个添加数据即可
@property RLMArray<ImgData *><ImgData> *imgDatas;
// 传的时间,(假设)服务器传过来的
// 没传值为:0;
@property NSTimeInterval time;
// 格式化后的时间,弱业务逻辑,自己的内容
@property NSString *timeFormatter;
@end
// This protocol enables typed collections. i.e.:
// RLMArray<student *><student>
// Realm中需要声明对象 -- 针对如果其他类要用到这个变量,而且是用数组封装的变量时
RLM_ARRAY_TYPE(Stu)
Stu.m
#import "Stu.h"
@implementation Stu
// 设置num为主键,即有值,且唯一
+(NSString *)primaryKey{
return @"num";
}
// 设置必须要有值的参数,没有设置会报错
+(NSArray<NSString *> *)requiredProperties{
return @[@"name"];
}
// 用get方法获取data
-(UIImage *)image{
return [UIImage imageWithData:self.imageData];
}
// Specify default values for properties -- 设置默认值,就是当值为空的时候,有个默认值
+ (NSDictionary *)defaultPropertyValues
{
return @{@"name":@"这是侠"};
}
// Specify properties to ignore (Realm won't persist these) -- 忽略的属性列表,就是可以不传的值
+ (NSArray *)ignoredProperties
{
return @[@"timeFormatter"];
}
@end
由于在Stu.h我引入了
@property RLMArray<ImgData *><ImgData> *imgDatas;
变量,为什么呢?我的想法是,图片的话,万一需求是一个类要传多张图片的时候,肯定是用数组包裹的,所以必须有个数组,数组传的是每一个imageData,如代码
ImgData.h&.m
#import <Realm/Realm.h>
NS_ASSUME_NONNULL_BEGIN
@interface ImgData : RLMObject
@property NSData *imgData;
@end
// 必须要声明是这个type,不然导入的一方会因为缺少代码而报错
RLM_ARRAY_TYPE(ImgData)
NS_ASSUME_NONNULL_END
#import "ImgData.h"
@implementation ImgData
@end
使用:增
-(void)realmAdd{
//1.快速Realm创建对象
//第一种
// Stu *stu1 = [[Stu alloc] initWithValue:@{@"num":@2,@"name":@"xxx"}];
// //第二种(顺序是和声明的顺序一致,目前不推荐采用)
// Stu *stu2 = [[Stu alloc] initWithValue:@[@"zhangsan",@26]];
//第三种(推荐这个,逻辑严密)
Stu *stu3 = [[Stu alloc] init];
stu3.name = @"网二";
stu3.num = 102;
//2.存储对象(realm种存储对象一定要开启事务)
RLMRealm *realm = [RLMRealm defaultRealm];
//第一种方法
//开启事务
[realm beginWriteTransaction];
//存储对象
[realm addObject:stu3];
//提交事务
[realm commitWriteTransaction];
//第二种方法(推荐这个,逻辑比较快)
[realm transactionWithBlock:^{
[realm addObject:stu3];
}];
//第三种 地址在po NSHomeDirectory(),在documents里
// [realm transactionWithBlock:^{
// [Stu createInRealm:realm withValue:@[@"zhangsan",@28]];
// }];
}
我这里把方法都柔和在一起了,希望能看得懂,我也选择了我喜欢的策略,附有“推荐二字”
改
RLMRealm *realm = [RLMRealm defaultRealm];
Stu *stu = [[Stu alloc] init];
stu.name = @"王瑞文";
stu.num = 104;
// stu.image = [UIImage imageNamed:@"video_supervision_video_bg"];
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"video_supervision_video_bg.png" ofType:nil];
NSData *data = [NSData dataWithContentsOfFile:filePath];
stu.imageData = data;
// 如果看到以下错误:
//Terminating app due to uncaught exception 'RLMException', reason: 'Migration is required due to the following errors:
//- Property 'Stu.name' has been made required.'
// 即表示需要进行数据迁移操作,在一个类里添加或者修改了一个参数,或者添加了一个方法声明,都会发生这个操作
// 由于表结构发生了改变,则需要发生数据迁移
// 设置data数组模型
ImgData *data1 = [[ImgData alloc]init];
data1.imgData = [NSData dataWithContentsOfFile:filePath];
ImgData *data2 = [[ImgData alloc]init];
data2.imgData = [NSData dataWithContentsOfFile:filePath];
// RLM数组可以使用NSMutableArray添加数组的方法
[stu.imgDatas addObject:data1];
[stu.imgDatas addObject:data2];
Stu *stu2 = [[Stu alloc] init];
stu2.name = @"王文瑞";
stu2.num = 102;
Stu *stu3 = [[Stu alloc] init];
stu3.name = @"网二小";
stu3.num = 101;
Stu *stu4 = [[Stu alloc] init];
stu4.name = @"王文瑞";
stu4.num = 103;
Stu *stu5 = [[Stu alloc] init];
stu5.num = 108;
// 添加或修改,就是如果没有与之主键冲突的就添加,有就修改,在自己不确定自己是否添加的时候,可以使用addOrUpdateObject方法
[realm transactionWithBlock:^{
[realm addOrUpdateObject:stu];
[realm addOrUpdateObject:stu2];
[realm addOrUpdateObject:stu3];
[realm addOrUpdateObject:stu4];
[realm addOrUpdateObject:stu5];
}];
这块位置,我不但推荐拿来修改,也可以拿来添加,保证了很高的容错率
删
-(void)deleteOperation{
RLMRealm *realm = [RLMRealm defaultRealm];
// 查询所有结果
//RLMResults *results = [Stu allObjects];
// 根据主键,查到这个模型(这个模型,必须是被realm数据库管理的模型),所以主键要写对
Stu *mainKeyRes = [Stu objectInRealm:realm forPrimaryKey:@101];
// 通过名字查询结果
RLMResults *results = [Stu objectsWhere:@"name = '王文瑞'"];
NSLog(@"%@",results);
// 获取结果集第一个然后删除
Stu *stu = results.firstObject;
// 删除的模型,一定要求是被realm所管理的
[realm transactionWithBlock:^{
// 删除所有数据
//[realm deleteAllObjects];
// 删除指定数据,但是这个指定数据,一定是要在数据库里的,不然会崩
[realm deleteObject:mainKeyRes];
RLMResults *Allresults = [Stu allObjects];
NSLog(@"%@",Allresults);
}];
}
查 (这里只列举了一般的,如果还有更多需求的,可以结合删除需求一起来看一看)
-(void)searchOpertaion{
RLMResults *Allresults = [Stu allObjects];
// 排序查询 ascending:NO由高到低 YES:由低到高
RLMResults *sortResults = [Allresults sortedResultsUsingProperty:@"num" ascending:YES];
NSLog(@"%@",sortResults);
}
查这个问题上,视频老师也提出了很多需求,比方说多线程,懒加载之类的,说实话我没有听太懂,可能是指,很消耗性能?所以到底是怎么使用呢?也可能是提示使用者查找数据要精细,精确到哪个数据点,还是说其他的用途
其他遇到的
例如导包的时候发觉 :导入realm.framework的时候出现image not found的处理https://blog.csdn.net/knaht/article/details/80505648
这个链接我调试过,也成功运行了,如果版本很高没遇到这个问题,那可以选择性忽略
数据库配置
我的意思就是可以生成不同数据库文件名的一种策略
// 用做切换用户的,就是生成不同文件名,产生不同的数据库
// 然后剩下注意的,就是生成不同的realm对象就可以了
// 比方说我有两个类,但是两个类不想存在一个数据库里,怎么区分呢?
-(void)setDefaultRealmForUser:(NSString *)username{
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 使用默认的目录,但是使用用户名来替换默认的文件名
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:username] URLByAppendingPathExtension:@"realm"];
// 将这个配置应用到默认的Realm 数据库当中
[RLMRealmConfiguration setDefaultConfiguration:config];
}
数据迁移
这个就是用在比方说,Stu类,我在以前的基础上添加了一个字段,然后存储,这样子,肯定跟之前的发生了冲突,Realm就会报错,提示
//Terminating app due to uncaught exception 'RLMException', reason: 'Migration is required due to the following errors:
//- Property 'Stu.name' has been made required.
需要迁移版本,所以就有如此做法
/**
数据迁移
*/
-(void)dataLeaper
{
// 先做数据迁移操作 -- 一般设置在'appdelegate didfinishLaunchingxxxxxxx'方法里面设置效率最好
// 获取默认配置
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 叠加版本号(要比上一次的版本号高)例如上一次是0这次就要比上次要高
int newVersion = 1;
config.schemaVersion = newVersion;
// 具体怎样迁移?
[config setMigrationBlock:^(RLMMigration * _Nonnull migration, uint64_t oldSchemaVersion) {
if (oldSchemaVersion < newVersion) {//判定,只有小于新版本的时候才有迁移操作
// 不需要做任何事情,就可以完成数据结构,已经数据的迁移
NSLog(@"需要做迁移动作");
}
}];
// 让配置生效
[RLMRealmConfiguration setDefaultConfiguration:config];
// 当然这些都是在配置,如果需要立即迁移,就要访问一下realm,而且迁移之后,之前的数据都在存在
[RLMRealm defaultRealm];
}
可能还有需求,比方说,我新建了两个字段"firstName","lastName",但是新的需求是,添加了一个"fullName",它="firName"+"lastName",这个冲突就打了,加新的,去老的,而且要传值,这个做法我就上图了
otherLeapNeed.png
这里的Migration就是一个类名,表示,在哪个类做这样子的数据配置啊?
newObject:新的对象,新的对象里的什么字段=旧的对象里的什么字段啊,这样子遍历下来,得出的,所以看得出,是一个耗时方法,它需要遍历数据库里每一个元素,进行更改操作
还有个需求,"fullName"字段改了,改成了"fuName",这个也是有可能的,版本又要迁移了,怎么办?如图:
shuxingbiangeng.png