iOS 数据持久化方案-Realm的使用
总体内容
1、Realm介绍
2、使用教程与辅助工具
3、Realm的具体使用
一、Realm介绍
- 1.1、Realm 是一个跨平台移动数据库引擎,支持
iOS
、OS X
(Objective-C
和Swift
)以及Android
,核心数据引擎C++
打造,并不是建立在SQLite之上的ORM, 是拥有独立的数据库存储引擎,具体详情。ORM:对象-关系映射(OBJECT/RELATIONALMAPPING,简称ORM),ORM技术是在对象和关系之间提供了一条桥梁。
- 1.2、Realm性能:比
sqlite
,coredata
牛逼 - 1.3、Realm使用方面:相比于sqlite, coredata, 使用起来更加简单, 更易入门。
二、使用教程与辅助工具
-
2.1、Realm 教程
Realm 教程页面 ,打开后等待一下会自动转化为中文界面
在上面的教程里面我们可以看到有很多语言,我们关注的是Swift
与OC
,导入Realm方式如下:-
导入方式一:CocoaPods 导入
-
Swift
pod 'RealmSwift'
-
OC
pod 'Realm'
建议在终端运行
pod repo update
以使CocoaPods最新的Realm版本。 -
-
导入方式二:手动导入,步骤如下
-
<1>、下载 Realm 的最新发布版本,并解压;
根据自己的需要选择相应的
Realm.framework
-
<2>、前往 Xcode 工程的 “General” 设置选项卡中,从
ios/dynamic/
、osx/
、tvos/
或者watchos/
目录中,将Realm.framework
拖曳到 “Embedded Binaries” 部分内。请确保勾选了 Copy items if needed(除非项目中有多个平台都需要使用 Realm ),然后单击 Finish按钮;提示:在把
添加动态库Realm.framework
拖进项目后一定要在TARGET
->General
->Embedded Binaries
里面把这Realm.framework
添加进来,否则运行会崩溃
-
<3>、在单元测试目标的 “Build Settings” 中,将
Realm.framework
的父目录添加到 “Framework Search Paths” 部分中;提示这一步一般在你把
Realm.framework
拖进项目就会自动完成 -
<4>、如果使用了 Realm Swift,请将
Swift/RLMSupport.swift
文件拖曳到 Xcode 工程的文件导航栏中,请确保选中了 Copy items if needed 选择框; -
<5>、如果在 iOS、watchOS 或者 tvOS 工程中使用 Realm,请在应用目标的 “Build Phases” 中创建一条新的 “Run Script Phase”,然后将下面这段代码粘贴到脚本文本框内:
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"
添加后的效果:
添加后的效果因为要绕过 App Store 出现的提交 bug, 因此这一步在打包通用二进制文件时是必须的。
-
-
-
2.2、Realm 辅助工具
- <1>、Realm Browser: 可视化访问 Realm 数据库,与SqliteManger 打开 Sqlite数据库一样,Realm Browser 用来打开 Realm 数据库
Realm Browser - <2>、Realm在xcode里面使用的插件Alcatraz(有点大),它 可以快速创建Realm可存储模型对象
插件-
下载后,打开运行一下
下载后,打开运行一下 -
查看是否插件安装成功,创建一个类,滚到最下面看是否有如下图所示:
查看是否插件安装成功
-
- <1>、Realm Browser: 可视化访问 Realm 数据库,与SqliteManger 打开 Sqlite数据库一样,Realm Browser 用来打开 Realm 数据库
三、Realm的具体使用,Demo
-
3.1、简单的数据操作:使用
RLMRealm
对象, 保存指定模型-
(1)、创建一个模型
创建属于Realm的modelStudent
继承于RLMObject
,看准了不是继承于NSObject
,如果在2.2
里面安装了插件并运行后,插件生效,就可以快速创建 Realm的model了,如下图
-
(2)、在Student类里面定义属性:由于Realm 在自己的引擎内部有很好的语义解释系统,所以 Objective‑C 的许多属性特性将被忽略,如nonatomic, atomic, strong, copy 和 weak 等。 因此为了避免误解,官方推荐在编写数据模型的时候不要使用任何的属性特性。
/** 学生的唯一ID */ @property int studentId; /** 学生的名字 */ @property NSString *studentName;
-
(3)、创建对象的方式
-
方式一:普通创建
Student *student = [[Student alloc]init]; student.studentId = @1; student.studentName = @"小王";
-
方式二:通过父类
RLMObject
中的方法initWithValue
快速创建,可以放字典,也可以放数组,如下// 数组 Student *student = [[Student alloc]initWithValue:@[@2,@"小冯"]; // 字典 Student *student = [[Student alloc]initWithValue:@{@"studentId":@2,@"studentName":@"小冯"}];
提示:放
数组
要与model
里面的属性顺序保持一致,放字典的话键值保持一致
注意:所有的必需属性都必须在对象添加到 Realm 前被赋值
-
-
(4)、使用
RLMRealm
对象, 保存指定模型,如下,我们采用initWithValue
快速创建并保存// 获取RLMRealm对象 RLMRealm *realm = [RLMRealm defaultRealm]; // 创建对象 Student *student = [[Student alloc]initWithValue:@{@"studentId":@2,@"studentName":@"小冯"}];
-
保存模型方式一:开启事务,写入数据,关闭事务
// 开启事务 [realm beginWriteTransaction]; // 写入数据 [realm addObject:student]; // 关闭事务 [realm commitWriteTransaction];
-
保存模型方式二:使用 block
[realm transactionWithBlock:^{ [realm addObject:student]; }];
-
保存模型方式三
[realm transactionWithBlock:^{ [Student createInRealm:realm withValue:@{@"studentId": @3, @"studentName": @"王小二"}]; }];
提示:所有的必需属性都必须在对象添加到 Realm 前被赋值,并且使用 Realm 对象写入值保存模型
- 还可以开启线程来保存模型
-
-
-
3.2、使用
RLMRealm
对象, 更新指定模型-
更新模型方式一:在事务中直接更新对象
Student *student = [[Student alloc]initWithValue:@{@"studentId":@3,@"studentName":@"王小二"}]; RLMRealm *realm = [RLMRealm defaultRealm]; // 保存模型 [realm transactionWithBlock:^{ // 写入数据 [realm addObject:student]; }]; // 更新模型:这里的更新模型一定是被realm所管理的模型 [realm transactionWithBlock:^{ // 写入数据 student.studentName = @"王冲"; }];
提示:上面的
student
已经被realm
所管理,而且已经和磁盘上的对象进行地址映射- 更新模型一定是被
realm
所管理的模
- 更新模型一定是被
-
更新模型方式二:根据 主键 进行更新
-
(1)、上面的Student我们还没有设置主键,现在在
Student.m
里面设置一下 主键/** 设置主键 */ +(NSString *)primaryKey{ return @"studentId"; }
-
(2)、在事务中调用方法:
[realm addOrUpdateObject:stu2];
,如下Student *student = [[Student alloc]initWithValue:@{@"studentId":@3,@"studentName":@"王小二"}]; RLMRealm *realm = [RLMRealm defaultRealm]; // 在事务里面做处理 [realm transactionWithBlock:^{ // 根据主键更新模型 [realm addOrUpdateObject:student]; }];
-
-
更新模型方式三:也是根据 主键 进行更新
-
(1)、设置主键和上面的一样
-
(2)、在事务中调用方法:
[模型类名 createOrUpdateInRealm:realm withValue:值];
,举例如下:RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [Student createOrUpdateInRealm:realm withValue:@{@"studentId":@3,@"studentName":@"二蛋子"}]; }];
提示:withValue:后面还可以跟数组,但是传值要和模型里面属性的顺序一致,如:
[Student createOrUpdateInRealm:realm withValue:@[@3,@"二蛋子"]]; }];
-
-
-
3.3、Realm 删除模型数据 :删除的模型一定是 realm 所管理的
-
分析:我们要删除模型,首先要获取模型,不能是我们自己创建出来的模型,必须是从 realm 数据库里面获取出来的模型,如下:
-
(1)、根据主键获取模型(结果只能是一个或者没有,所以直接用查询的模型接收)
Student *student = [Student objectForPrimaryKey:@2];
-
(2)、通过sql 语句查询模型:因为根据名字查询的结果可能多个模型,采用 RLMResults 结果数组接收
RLMResults *results2 = [Student objectsWhere:@"studentName = '二傻'"];
-
-
删除方式一:删除单个模型
// 根据主键获取模型 Student *student = [Student objectForPrimaryKey:@2]; RLMRealm *realm = [RLMRealm defaultRealm]; // 删除单个模型 [realm transactionWithBlock:^{ [realm deleteObject:student]; }];
-
删除方式二:通过sql 语句查询模型:因为根据名字查询的结果可能多个模型
// 通过sql 语句查询模型:因为根据名字查询的结果可能多个模型 RLMResults *results2 = [Student objectsWhere:@"studentName = '二傻'"]; RLMRealm *realm = [RLMRealm defaultRealm]; // 删除查询出来的 模型 数组 [realm transactionWithBlock:^{ [realm deleteObjects:results2]; }];
当然你可以可以对
RLMResults
进行遍历删除或者for循环逐个删除,但是上面的删除还是挺方便的,Realm 内部做了遍历删除 -
删除方式三:删除某一特定类型的模型所有数据,如删除
Student
模型的所有数据// 获取 Student 模型的所有数据 RLMResults *results = [Student allObjects]; RLMRealm *realm = [RLMRealm defaultRealm]; // 在事务里面删除模型里面的所有数据 [realm transactionWithBlock:^{ [realm deleteObjects:results]; }];
-
删除方式四: 删除
Realm
里面所有的模型数据(比如删除 Student、Dog 模型等等,全部数据删除)RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm deleteAllObjects]; }];
提示:删除的操作一定要放到 事务里面,我一般采用block的事务方式,当然你可以选择其他的方式,如上面
3.1
中的(4)
-
-
3.4、 使用
RLMRealm
对象, 查询数据-
(1)、注意事项
-
所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。
解释:当我们在获取一个查询
RLMResults *results = [Student allObjects];
时,并没有读取到数据,当真正的操作数据的属性的时候才能够读取相应的数据。 -
查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。
解释:在我们查询一个模型后,所获取的数据不是拷贝出来的,而是直接操控数据库的
-
一旦检索执行之后, RLMResults 将随时保持更新
解释:在下面代码中第一次打印
results
会把数据库中的结果打印出来,当添加一个数据后,再次打印会把添加的数据和之前的数据都会打印出来,这就是所谓的:一旦检索执行之后, RLMResults 将随时保持更新RLMResults *results = [Student allObjects]; NSLog(@"第 1 次打印结果:%@",results); Student *student = [[Student alloc]initWithValue:@{@"studentId":@1,@"studentName":@"小王"}]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:student]; }]; NSLog(@"第 2 次打印结果:%@",results);
-
-
(2)、查询方式一:查询某一个模型的查询所有内容
RLMResults *results = [Student allObjects]; NSLog(@"打印结果:%@",results);
-
(3)、查询方式二:条件查询(可以编辑自己需要的sql语句),更多的sql语句可以参考的的 Mysql的笔记,里面有更多的 sql 查询语句
RLMResults<Student *> *students = [Student objectsWhere:@"studentName = '小王'"]; NSLog(@"打印结果:%@",students);
-
(4)、查询方式三:对查询结果排序
查询并排序的结果RLMResults<Student *> *students = [Student allObjects]; NSLog(@"没有排序的结果:%@",students); RLMResults *soreStudents = [students sortedResultsUsingKeyPath:@"studentId" ascending:YES]; NSLog(@"排序后的结果:%@",soreStudents);
-
(5)、查询方式四:链式查询 (在查询结果的基础上, 进行二次查询)
链式查询RLMResults<Student *> *students1 = [Student allObjects]; NSLog(@"第 1 次查询的结果:%@",students1); RLMResults<Student *> *students2 = [Student objectsWhere:@"studentName = '小王'"]; NSLog(@"第 2 次查询的结果:%@",students2);
-
(6)、查询方式五:分页 查询,先看一个基础知识,更多的可以看我的 数据库查询的第五部分
数据库分页查询的基础知识
Realm官网关于分页的讲解我们在这里采用先拿到相应条件下的数据,再根据分页(利用for循环)来展示数据 RLMResults<Student *> *students = [Student allObjects]; for (NSInteger i = 0; i < 2; i++) { Student *student = students[I]; NSLog(@"结果是:%@",student); }
-
(7)、Filtering(过滤):
NSPredicate
,以下内容将通过调用[RLMObject objectsWhere:]从默认Realm中检索名称以“小”开头的所有学生来扩展我们之前的示例:
Filtering(过滤)// Query using a predicate string:使用谓词字符串进行查询 RLMResults<Student *> *students1 = [Student objectsWhere:@"studentName BEGINSWITH '小'"]; NSLog(@"students1=%@",students1); // Query using an NSPredicate:使用NSPredicate查询 NSPredicate *pred = [NSPredicate predicateWithFormat:@"studentName BEGINSWITH %@",@"小"]; RLMResults<Student *> *student2 = [Student objectsWithPredicate:pred]; NSLog(@"students2=%@",student2);
提示:有关构建谓词和使用我们的NSPredicate Cheatsheet的更多信息,请参阅Apple的Predicates编程指南。Realm支持许多常见谓词:
- 比较操作数可以是属性名称或常量。至少有一个操作数必须是属性名称。
- 比较操作符==,<= ,<,> =,>,!=,和BETWEEN都支持
int
,long
,long long
,float
,double
,和NSDate
属性类型,例如age == 45
- 身份比较==,!=,例如
[Employee objectsWhere:@"company == %@", company]
。 - 布尔属性支持比较运算符==和!=。
- 对于
NSString
和NSData
属性,支持==,!=,BEGINSWITH,CONTAINS和ENDSWITH运算符,例如name CONTAINS 'Ja'
- 对于
NSString
属性,LIKE运算符可用于将左手属性与右手表达式进行比较:?
并且*
允许作为通配符,其中?
匹配1个字符并*
匹配0个或更多个字符。示例:value LIKE '?bc*'
匹配“abcde”和“cbc”等字符串。 - 字符串的不区分大小写的比较,例如
name CONTAINS[c] 'Ja'
。请注意,只有字符“AZ”和“az”才会被忽略。在[c] modifier can be combined with the
[d]`改性剂。 - 字符串的变音符号不敏感比较,例如
name BEGINSWITH[d] 'e'
匹配étoile。此修饰符可与[c]
修饰符组合使用。(此修饰符只能应用于Realm支持的字符串子集:请参阅详细信息的限制。) - Realm支持以下复合运算符:“AND”,“OR”和“NOT”,例如
name BEGINSWITH 'J' AND age >= 32
。 - 收容操作数IN,例如
name IN {'Lisa', 'Spike', 'Hachi'}
- 无比较==,!=,例如
[Company objectsWhere:@"ceo == nil"]
。请注意,Realm将其nil
视为特殊值而不是缺少值; 与SQL不同,nil
等于自己。 -
任何比较,例如
ANY student.age < 21
。 - 支持和属性的聚合表达式@ count,@ min,@ max,@ sum和@avg,例如,查找所有员工人数超过五人的公司。
RLMArray``RLMResults``[Company objectsWhere:@"employees.@count > 5"]
- 子查询受以下限制支持:
- @count是唯一可以应用于SUBQUERY表达式的运算符。
- 的
SUBQUERY(…).@count
表达式必须以恒定的相比较。 - 尚不支持相关的子查询。
-
-
3.5、Realm 支持的数据类型
-
(1)、支持的数据类型:
BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData、NSNumber
不支持集合类型,比如:NSSarray 不能直接存储
-
(2)、不支持集合类型的解决方案
- 序列化成
NSData
进行存储 - 转换成
RLMArray<RLMObject>
进行存储
- 序列化成
-
(3)、比如:存储 image图片,我们不能直接存储
UIImage
,我们可以存储NSData
,如下定义@property NSData *imageData;
提示:
@property(readonly) UIImage *image;
其中readonly
在Realm 模型里面是自动忽略的意思,当然你也可以把要忽略的字段放到下面数组里面+ (NSArray *)ignoredProperties { return @[@"image"]; }
在学生 Student 里面定义狗数组,如下
-
-
3.6、Realm 关系
-
(1)、对一关系:多对一或一对一关系
比如一个学生有一只狗,那么我们就可以在学生里面定义一个狗的属性,如下@property Dog *dog;
在使用的时候和其他的属性一样,正常的赋值即可
对一关系:多对一或一对一关系Student *student = [[Student alloc]init]; student.studentId = 1; student.studentName = @"小明"; Dog *dog = [[Dog alloc]init]; dog.dogId = 1; dog.dogName = @"旺财"; student.dog = dog; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:student]; }];
-
(2)、对多关系:
定义数组遵守协议
举例:一个学生有多只狗,那么我们需要在学生里面定义一个狗的数组,但是这个数组格式有一定要求,在狗的类.h里面遵守RLM_ARRAY_TYPE(Dog)
,一般在安装了插件后在创建model的时候就会自动遵守这个协议
在学生里面定义狗数组,如下@property RLMArray<Dog *><Dog> *dogs;
在使用的时候,给小明两只狗
对多关系Student *student = [[Student alloc]init]; student.studentId = 1; student.studentName = @"小明"; Dog *dog1 = [[Dog alloc]init]; dog1.dogId = 1; dog1.dogName = @"旺财"; Dog *dog2 = [[Dog alloc]init]; dog2.dogId = 2; dog2.dogName = @"阿福"; [student.dogs addObject:dog1]; [student.dogs addObject:dog2]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:student]; }];
-
(3)、反向关系:关系是单向的。就拿我们的两个类Student,并Dog作为一个例子。如果Student.dogs链接到Dog实例,则可以按照链接从Student a到a Dog,但是无法从a Dog到其Student对象。您可以设置Dog.owner链接到的一对一属性Student,但这些链接彼此独立。添加一个Dog to Student.dogs不会将该狗的Dog.owner属性设置为正确Student。为解决此问题,Realm提供链接对象属性以表示反向关系。
@interface Dog : RLMObject /** 狗的唯一ID */ @property int dogId; /** 狗的名字 */ @property NSString *dogName; @property (readonly) RLMLinkingObjects *owners; @end @implementation Dog /** 设置主键 */ +(NSString *)primaryKey{ return @"dogId"; } + (NSDictionary *)linkingObjectsProperties { return @{@"owners":[RLMPropertyDescriptor descriptorWithClass:NSClassFromString(@"Student") propertyName:@"dogs"]}; } @end
提示:
-
@property (readonly) RLMLinkingObjects *owners;
中的 readonly 是忽略的意思,不会被写入数据库 - 上面
linkingObjectsProperties
方法里面第一个参数是类对象,第二个是相应类里面的属性,通过链接对象属性,您可以从特定属性获取链接到给定对象的所有对象。一个Dog对象可以有一个名为属性owners包含所有的Student有这个确切的对象Dog在他们的对象dogs属性。创建owners类型的属性,RLMLinkingObjects然后覆盖+[RLMObject linkingObjectsProperties]以指示owners与Student模型对象的关系。
在使用的时候,如下,通过 Dog 查询其主人的信息
在使用的时候,如下,通过 Dog 查询其主人的信息RLMResults *results = [Student allObjects]; Student *student = results.firstObject; Dog *dog = student.dogs.firstObject; NSLog(@"owners=%@",dog.owners);
-
-
-
3.7、Realm消息机制:可空属性&默认值&忽略属性
-
(1)、可空属性:默认情况下, 属性值可空, 如果强制要求某个属性非空, 可以使用如下方法,比如,我们要num属性必须有值,可以在像一个的model的.m 如下设置,在保存模型的时候,如果不赋值会报错的
+(NSArray<NSString *> *)requiredProperties{ return @[@"num"]; }
-
(2)、默认值:我们在给一些属性赋值的时候,如果不赋值,我们想有一个默认的值,可以在相应的model的.m里面的下面方法设置对应属性的值,如下
+ (NSDictionary *)defaultPropertyValues { return @{@"catName":@"小花"}; }
-
(3)、忽略属性
-
忽略属性一:使用
readonly
来忽略这个属性,也就是不用写入数据库,如下/** 猫的年龄 */ @property(readonly) NSInteger catAge;
-
忽略属性二:在对应model 的 .m里面设置,如下
+ (NSArray *)ignoredProperties { return @[@"catAge"]; }
-
-
-
3.8、通知
通知-
(1)、通知概念
- 可以注册侦听器以接收有关Realm或其实体的更改的通知。当Realm作为一个整体被更改时发送领域通知 ; 更改,添加或删除单个对象时会发送收集通知。
- 只要对返回的通知令牌进行引用,就会传递通知。您应该在注册更新的类上保留对此标记的强引用,因为在取消分配通知令牌时会自动取消注册通知。
- 通知始终在最初注册的线程上提供。该线程必须具有当前运行的运行循环。如果您希望在主线程以外的线程上注册通知,则您负责在该线程上配置和启动运行循环(如果尚不存在)。
- 在提交每个相关的写事务之后异步调用通知处理程序,无论写事务发生在哪个线程或进程上。
- 如果在启动写入事务时将Realm提升到最新版本,则可能会同步调用通知处理程序。如果在Realm进入最新版本时,将以触发通知的方式修改或删除正在观察的Realm实体,则会发生这种情况。此类通知将在当前写入事务的上下文中运行,这意味着尝试在通知处理程序中开始写入事务将导致Realm抛出异常。如果您的应用程序的架构设置可能会出现这种情况,您可以使用它
-[RLMRealm isInWriteTransaction]
来确定您是否已经在写入事务中。 - 由于使用运行循环传递通知,因此运行循环上的其他活动可能会延迟通知的传递。当无法立即传递通知时,多个写入事务的更改可能会合并为单个通知。
-
(2)、领域通知:通知处理程序可以在整个Realm上注册。每次提交涉及该Realm的写入事务时,无论写入事务发生在哪个线程或进程上,都将触发通知处理程序:
-
举例:
创建一个强引用通知对象
@property (nonatomic, strong) RLMNotificationToken *token;
创建通知
RLMRealm *realm = [RLMRealm defaultRealm]; // 创建通知 self.token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) { NSLog(@"监听到修改通知"); }];
添加数据测试
NotificationModel *model = [[NotificationModel alloc]init]; model.number = 1; model.name = @"测试1"; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:model]; }];
销毁通知
[self.token invalidate];
-
-
(3)、领域通知:
- 收集通知不会收到整个Realm,而是收到细粒度的更改说明。它们包括自上次通知以来已添加,删除或修改的对象索引。收集通知是异步传递的,首先是初始结果,然后是每次写入事务后再次发送,这会改变集合中的任何对象(或添加新对象)。
- 可以通过
RLMCollectionChange
传递给通知块的参数访问这些更改。这个对象保存有关受索引信息deletions
,insertions
和modifications
。 - 前两个,删除和插入,在对象开始和停止成为集合的一部分时记录索引。这会将对象添加到Realm或从Realm中删除它们时考虑在内。为此,
RLMResults
当您筛选特定值并更改对象以使其现在与查询匹配或不再匹配时也适用。对于基于RLMArray
或RLMLinkingObjects
包括派生的集合,RLMResults
当在关系中添加或删除对象时,这也适用。 - 只要集合中对象的属性发生更改,您就会收到有关修改的通知。这也发生更改的一对一和一对多的关系,虽然通知不会采取反向关系考虑在内。
-
举例1:
创建一个强引用通知对象@property (nonatomic, strong) RLMNotificationToken *token;
创建通知
RLMResults *results = [NotificationModel allObjects]; self.token2 = [results addNotificationBlock:^(RLMResults * _Nullable results, RLMCollectionChange * _Nullable change, NSError * _Nullable error) { NSLog(@"%@--%@--%@", results, change, error); }];
提示:results并不一定是全部的数据,你可以通过查询去获取一些数据,当这个结果集的数据发生变化就会走这个通知
销毁通知
[self.token invalidate];
添加数据测试
NotificationModel *model = [[NotificationModel alloc]init]; model.number = 2; model.name = @"测试2"; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:model]; }]; [realm transactionWithBlock:^{ [realm deleteObject:model]; }]; NotificationModel *model2 = [[NotificationModel alloc]init]; model2.number = 3; model2.name = @"测试3"; [realm transactionWithBlock:^{ [realm addObject:model2]; }];
-
举例2:我们可以看如果在tableview上面的数据变化时候的代码
- (void)viewDidLoad { [super viewDidLoad]; // Observe RLMResults Notifications __weak typeof(self) weakSelf = self; self.notificationToken = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *changes, NSError *error) { if (error) { NSLog(@"Failed to open Realm on background worker: %@", error); return; } UITableView *tableView = weakSelf.tableView; // Initial run of the query will pass nil for the change information if (!changes) { [tableView reloadData]; return; } // Query results have changed, so apply them to the UITableView [tableView beginUpdates]; [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates]; }]; } - (void)dealloc { [self.notificationToken invalidate]; }
-
(4)、对象通知
-
Realm支持对象级通知。您可以在特定Realm对象上注册通知,以便在删除对象时或在对象上的任何托管属性修改其值时收到通知。(这也适用于将其值设置为其现有值的托管属性。)
-
只有Realm管理的对象可能在其上注册了通知处理程序。
-
对于在不同线程或不同进程中执行的写入事务,当管理对象的Realm(自动)刷新到包含更改的版本时,将调用该块,而对于本地写入事务,它将在某个时刻被调用。写入事务提交后的未来。
-
通知处理程序有三个参数。第一个参数deleted是BOOL指示对象是否已删除。如果是这样YES,其他参数将为nil,并且永远不会再次调用该块。
-
第二个参数,changes是一个NSArray的RLMPropertyChange对象。这些对象中的每一个都包含已更改的属性的名称(作为字符串),前一个值和当前值。
-
第三个论点是NSError。如果发生涉及对象的错误,NSError则将包含有关发生的事件的信息,changes将为nil,deleted将是NO,并且将永远不再调用该块。
@interface RLMStepCounter : RLMObject @property NSInteger steps; @end @implementation RLMStepCounter @end RLMStepCounter *counter = [[RLMStepCounter alloc] init]; counter.steps = 0; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:counter]; [realm commitWriteTransaction]; __block RLMNotificationToken *token = [counter addNotificationBlock:^(BOOL deleted,NSArray<RLMPropertyChange *> *changes,NSError *error) { if (deleted) { NSLog(@"The object was deleted."); } else if (error) { NSLog(@"An error occurred: %@", error); } else { for (RLMPropertyChange *property in changes) { if ([property.name isEqualToString:@"steps"] && [property.value integerValue] > 1000) { NSLog(@"Congratulations, you've exceeded 1000 steps."); [token invalidate]; token = nil; } } } }];
-
-
-
3.9、Realm数据库操作 (不同的用户, 使用不同的数据库文件)
-
(1)、不同的用户, 创建不同的数据库(用户在登陆后,我们就进行设置相应的数据库,databaseName 是数据库的名字,以
.realm
结尾)+(void)setDefaultRealmForUserDatabaseName:(NSString *)databaseName{ RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // 使用默认的目录,但是使用用户名来替换默认的文件名 config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent] URLByAppendingPathComponent: databaseName] URLByAppendingPathExtension:@"realm"]; // 将这个配置应用到默认的 Realm 数据库当中 [RLMRealmConfiguration setDefaultConfiguration:config]; }
使用举例,我们以
不同的用户, 创建不同的数据库DataBaseModel
为例
// 数据库的配置 [RLMRealmDataBaseTools setDefaultRealmForUserName:@"王二"]; // 创建数据model DataBaseModel *model = [DataBaseModel new]; model.number = 1; // 获取对应数据库的 RLMRealm RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ // 写入数据 [realm addObject:model]; }];
-
(2)、只读数据库的设置
+(void)setDefaultRealmReadOnly{ RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // 以只读模式打开文件,因为应用数据包并不可写 config.readOnly = YES; [RLMRealmConfiguration setDefaultConfiguration:config]; }
-
(3)、删除指定用户的数据库
+(void)deleteDatabaseName:(NSString *)databaseName{ [self setDefaultRealmForUserDatabaseName:databaseName]; NSFileManager *manager = [NSFileManager defaultManager]; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; NSArray<NSURL *> *realmFileURLs = @[ config.fileURL, [config.fileURL URLByAppendingPathExtension:@"lock"], [config.fileURL URLByAppendingPathExtension:@"management"], ]; for (NSURL *URL in realmFileURLs) { NSError *error = nil; [manager removeItemAtURL:URL error:&error]; if (error) { // 处理错误 } } }
-
-
3.10、Realm 数据库迁移(适用于修改了数据模型的情况)
-
(1)、适用于修改了数据模型的情况:比如在一个模型里面我们删除了一个属性或者添加了一个属性的情况等等,结构发生了变化
-
(2)、数据结构迁移 (增加或者减少属性):在
[AppDelegate didFinishLaunchingWithOptions:]
中进行配置+(void)databaseMigration:(int)newVersion{ // 1. 获取默认配置 RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // 2. 叠加版本号(要比上一次的版本号高) 0 config.schemaVersion = newVersion; // 3. 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用 [config setMigrationBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion) { // 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构 if (oldSchemaVersion < newVersion) { } }]; // 4. 让配置生效(告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象) [RLMRealmConfiguration setDefaultConfiguration:config]; // 5. 我们已经告诉了 Realm 如何处理架构的变化,打开文件之后将会自动执行迁移,数据结构会发生变化 [RLMRealm defaultRealm]; }
-
(3)、数据迁移(比如说想要把某些数据用其他的数据字段表示,我们可以在上面代码的block做操作)
if (oldSchemaVersion < newVersion) { NSLog(@"需要做迁移动作"); //执行更名动作 // [migration renamePropertyForClass:@"Migration" oldName:@"fullName" newName:@"fullName2"]; // 无需做任何事情, 就可以完成, 数据结构, 以及数据的迁移 [migration enumerateObjects:@"模型的名字" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { newObject[@"新的字段名字"] = [NSString stringWithFormat:@"%@", oldObject[@"旧的字段名字"]]; }]; }
提示:这里可以把多个字段拼接为一个字段,如:
newObject[@"新的字段名字"] = [NSString stringWithFormat:@"%@", oldObject[@"旧的字段名字1"], oldObject[@"旧的字段名字2"]];
-
(4)、属性重命名(上面代码的block做操作)
if (oldSchemaVersion < newVersion) { NSLog(@"需要做迁移动作"); //执行更名动作 [migration renamePropertyForClass:@"模型名字" oldName:@"旧字段名" newName:@"模型里新字段名"]; }
-
(5)、多版本增量式迁移(线性迁移)
-
假如说,我们的应用有两个用户:JP和Tim,JP经常更新应用,但Tim却经常跳过某些版本。所以JP可能下载过这个应用的每一个版本,并且一步一步地跟着更新构架:第一次下载更新后,数据库架构从v0更新到v1;第二次架构从v1更新到v2 ...以此类推,井然有序。相反,Tim很有可能直接从v0版本直接跳到了v2版本。因此,应该您使用非嵌套的 if (oldSchemaVersion < X)结构来构造您的数据库迁移模块,以确保无论用户在使用哪个版本的架构,都能完成必需的更新。
-
当你的用户不按套路出牌,跳过有些更新版本的时候,另一种情况也会发生。假如您在v2里删掉了一个“email”属性,然后在v3里又把它重新引进了。假如有个用户从v1直接跳到v3,那Realm不会自动检测到v2的这个删除操作,因为存储的数据架构和代码中的架构吻合。这会导致Tim的人对象有一个v3的电子邮件属性,但里面的内容却是v1的。这个看起来没什么大问题,但是假如两者的内部存储类型不同(比如说:从ISO email标准格式变成了自定义格式),那麻烦就大了。为了避免这种不必要的麻烦,我们推荐您在if (oldSchemaVersion < 3)语句中,清空所有的电子邮件属性。
-
举例如下:核心代码
enumerateObjects:block:
遍历了存储在 Realm 文件中的每一个“Student
”对象[migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // 只有当 Realm 数据库的架构版本为 0 的时候,才添加 “studentName” 属性 if (oldSchemaVersion < 1) { newObject[@"studentName"] = @""; } // 只有当 Realm 数据库的架构版本为 0 或者 1 的时候,才添加“email”属性 if (oldSchemaVersion < 2) { newObject[@"email"] = @""; } // 只有当 Realm 数据库的架构版本为 2 的时候,去掉“email”属性 if (oldSchemaVersion < 3) { // 在后面执行 [RLMRealm defaultRealm]; 后会自动更新模型结构 } }];
-
-
最后:说一下 Realm 使用的话对项目会植入很深,因为它创建的model 都是继承于RLMObject
,相应模型里面的数组也是 RLMArray
类型,我们可以看出如果在项目去掉Realm
是一件很麻烦的事情,但是它的功能是很强大的;前面面我们所学的 iOS Sqlite数据库的使用 对项目的植入很小,功能不是很强大,基本上够用;具体用什么自己决定了