- Realm是由美国
YCombinator
孵化的创业团队历时几年打造,第一个专门针对移动平台设计的数据库
- Realm是一个跨平台的移动数据库引擎,目前支持
iOS
、Android
平台,同时支持Objective-C
、Swift
、Java
、React Native
、Xamarin
等多种编程语言
Realm
并不是对SQLite
或者CoreData
的简单封装, 是由核心数据引擎C++
打造,是拥有独立的数据库存储引擎,可以方便、高效的完成数据库的各种操作
Realm
简单介绍
Realm
的优势与亮点
- 开源。
Realm
移动端数据库相关代码已全部开源。数千开发者在GitHub
上参与了相关工作。另外还有几百个Realm
数据库相关的扩展。
- 简单易用:
Core Data
、SQLite
庞大的学习量和繁杂的代码足以吓退绝大多数刚入门的开发者,而换用Realm
,则可以极大地减少学习代价和学习时间,让应用及早用上数据存储功能
- 跨平台:现在绝大多数的应用开发并不仅仅只在
iOS
平台上进行开发,还要兼顾到Android
平台的开发。为两个平台设计不同的数据库是不明智的,而使用Realm
数据库,iOS
和Android
无需考虑内部数据的架构,调用Realm
提供的API
就可以完成数据的交换
- 线程安全。程序员无需对在不同线程中,对数据库的读取一致性做任何考虑,
Realm
会保证每次读取都得到一致的数据
可视化工具Realm Browser
为了配合Realm
的使用,Realm
还提供了一个轻量级的数据库查看工具Realm Browser
,借助这个工具,开发者可以查看数据库当中的内容,并执行简单的插入和删除操作。Realm Browser
可以在App Store
中下载安装
如果需要调试, 可以通过NSHomeDirectory()
打印出Realm
数据库地址, 找到对应的Realm
文件, 然后用Realm Browser
可视化工具打开即可
Realm
的安装
Realm的Github地址
手动安装
当使用手工方式安装Realm时,可以按照如下步骤进行
- 登录Realm官方网站或者Github,下载
Realm
的最新版本并解压
- 将
Realm.framework
从ios/static/
文件夹拖曳到您Xcode
项目中的文件导航器当中, 确保Copy items if needed
选中然后单击Finish
- 在
Xcode
文件导航器中选择您的项目,然后选择您的应用目标,进入到Build Phases
选项卡中。在Link Binary with Libraries
中单击 +
号然后添加libc++.tbd
以及libz.tbd
使用CocoaPods
安装
当使用CocoaPods
方式安装Realm
时,以Objective-C
为例
CocoaPods
版本要求是1.1.0
及以上版本
- 在
Podfile
中,添加pod 'Realm'
,如有需要, 添加pod 'Realm/Headers'
到测试项目中
- 在终端运行
pod install
即可安装
- 在
Swift
中需要输入pod 'RealmSwift'
才可以安装
- 如果是混编项目,就需要安装OC的
Realm
, 然后要把 Swift/RLMSupport.swift文件一同编译进去
RLMSupport.swift
这个文件为Objective-C
版本的Realm
集合类型中引入了Sequence
一致性,并且重新暴露了一些不能够从Swift
中进行原生访问的Objective-C
方法,例如可变参数variadic arguments
等, 更加详细的说明见官方文档
Xcode插件
Realm
提供了一个Xcode
插件,来方便的创建RLMObject
类,这需要我们首先安装相关的插件
- 打开
Realm
文件夹中的plugin/RealmPlugin.xcodeproj
并进行编译,重启Xcode
之后插件即可生效
- 当需要新建
RLMObject
类时,在新建类的选项中选择Realm Model Object
即可
Realm
的类定义说明
在Realm
框架中,定义了二十个核心类、常量、枚举类型、协议等,常用的如:RLMRealm
类、RLMObject
类、RLMResults
类等, 我们可以从Realm
的官方网站上查看所有的定义以及使用说明
RLMRealm类
- 一个
RLMRealm
类的对象可以认为是一个Realm
的数据库。Realm
数据库既可以存储在硬盘上,同时也可以存储在内存中
Realm
是框架的核心所在,是我们构建数据库的访问点,就如同Core Data
的管理对象上下文managed object context
一样
RLMRealm
类中,常用的属性或方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| + (instancetype)defaultRealm;
+ (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error;
+ (instancetype)realmWithPath:(NSString *)path;
- (void)beginWriteTransaction;
- (void)commitWriteTransaction;
- (BOOL)commitWriteTransaction:(NSError **)error;
- (BOOL)commitWriteTransactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens error:(NSError **)error;
- (void)cancelWriteTransaction;
- (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block;
- (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error;
- (void)addObject:(RLMObject *)object;
- (void)addOrUpdateObject:(RLMObject *)object;
- (void)addObjects:(id<NSFastEnumeration>)objects; - (void)addOrUpdateObjects:(id<NSFastEnumeration>)objects;
- (void)deleteObject:(RLMObject *)object; - (void)deleteObjects:(id)array; - (void)deleteAllObjects;
|
RLMObject类
- 在
Realm
数据库中存储的都是RMObject
对象,RLMObject
类是所有可以存储在Realm
数据库中的对象的根类
- 凡是可以存储在
Realm
数据库中的对象都是RLMObject
类或RLMObject
类的子类
- 要创建一个数据模型,我们只需要继承
RLMObject
,然后设计我们想要存储的属性即可
- 在
RLMObject
类中,我们可以添加属性,添加的属性类型可以支持如下类型:
NSString
:字符串
NSInteger
, int
, long
, float
, double
:数字型,注意没有CGFloat
BOOL/bool
:布尔型
NSDate
:日期型
NSData
:二进制字符型
NSNumber<X>
: 其中X
必须RLMInt
, RLMFloat
, RLMDouble
或 RLMBool
类型
RLMArray<X>
: 其中X
必须是RLMObject
类的子类, 用于建模多对多关系
RLMObject
的子类,用于建模多对一关系
RLMObject
类中,比较常用如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - (nonnull instancetype)initWithValue:(nonnull id)value;
+ (RLMResults *)allObjects;
+ (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...;
+ (nullable instancetype)objectForPrimaryKey:(nullable id)primaryKey;
+ (nonnull RLMResults *)allObjectsInRealm:(nonnull RLMRealm *)realm;
+ (nonnull RLMResults *)objectsInRealm:(nonnull RLMRealm *)realm where:(nonnull NSString *)predicateFormat, ...;
|
RLMResults类
- 当我们执行一个查询操作后,查询出满足条件的
RLMObject
对象会存放在一个RLMResults
对象中
RLMResults
类是一个数组类型的数据结构,因此在其类定义中,提供了很多与数组类似的属性和方法
1 2 3 4 5 6 7 8 9 10 11
| @property (readonly, assign, nonatomic) NSUInteger count;
@property (readonly, assign, nonatomic) RLMPropertyType type;
@property (readonly, nonatomic) RLMRealm *_Nonnull realm;
@property (readonly, copy, nonatomic, nullable) NSString *objectClassName;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| - (nullable RLMObjectType)firstObject;
- (nullable RLMObjectType)lastObject;
- (RLMObjectType)objectAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfObject:(RLMObjectArgument)object;
- (NSUInteger)indexOfObjectWhere:(nonnull NSString *)predicateFormat, ...;
- (RLMResults<RLMObjectType> *)objectsWhere:(NSString *)predicateFormat, ...;
- (RLMResults<RLMObjectType> *)sortedResultsUsingKeyPath:(NSString *)keyPath ascending:(BOOL)ascending;
- (nonnull RLMResults<RLMObjectType> *)distinctResultsUsingKeyPaths:(nonnull NSArray<NSString *> *)keyPaths;
|
https://realm.io/docs/objc/latest/api/Classes.html
Realm
的使用
Realm
中一些常用的类及其类的属性和方法上面已经介绍了, 下面我们就介绍Realm
的使用方法
创建RLMObject类
我们首先创建一个Student
类,该类是RLMObject
类的一个子类, 下图就是按照之前安装的Xcode
插件创建的
- 在
Student
添加两个属性, RLMObject
官方建议在RLMObject
类中添加的属性,是不需要指定属性关键字的,完全交由Realm
处理
- 假如设置了,这些
attributes
会一直生效直到RLMObject
被写入realm
数据库
RLM_ARRAY_TYPE
宏创建了一个协议,从而允许 RLMArray<Car>
语法的使用
- 如果
RLM_ARRAY_TYPE
宏没有放置在模型接口的底部的话,您或许需要提前声明该模型类
1 2 3 4 5 6 7
| @interface Student : RLMObject
@property int num; @property NSString *name;
@end RLM_ARRAY_TYPE(Student)
|
存储操作
- 对于
RLMObject
类型的对象,我们可以直接对创建的对象进行存储
- 第一步, 初始化对象
1 2 3 4 5 6 7 8 9 10
| Student *stu1 = [[Student alloc]initWithValue:@[@1, @"jun"]];
Student *stu2 = [[Student alloc]initWithValue:@{@"num": @2, @"name":@"titan"}];
Student *stu3 = [[Student alloc]init]; stu3.num = 3; stu3.name = @"titanjun";
|
第二步就是把RLMObject
对象写入Realm
数据库, 同样有三种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addObject:stu1];
[realm commitWriteTransaction];
RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:webSite1]; [realm addObject:webSite2]; }];
[realm transactionWithBlock:^{ [Student createInRealm:realm withValue:@{@"num": @3, @"name":@"coder"}]; }];
|
- 所有的必需属性都必须在对象添加到
Realm
前被赋值
- 如果在进程中存在多个写入操作的话,那么单个写入操作将会阻塞其余的写入操作,并且还会锁定该操作所在的当前线程
- 建议常规的最佳做法:将写入操作转移到一个独立的线程中执行
- 官方给出的建议:由于
Realm
采用了MVCC
设计架构,读取操作并不会因为写入事务正在进行而受到影响
- 除非您需要立即使用多个线程来同时执行写入操作,不然您应当采用批量化的写入事务,而不是采用多次少量的写入事务
- 下面的代码就是把写事务放到子线程中去处理
1 2 3 4 5 6 7
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject: stu4]; }]; });
|
查询操作
Realm
中也提供了功能强大的数据查询能力,如果会使用SQL
语言的话,上手的难度更低
- 在
Realm
的查询功能中,也可以像SQL
一样使用各种条件查询关键字,查询的结果会保存在一个RLMResults
类的数组中
- 全量查询, 通过调用
allObjects
方法, 得到该表中的所有数据
- 条件查询,设置一些查询条件,从而查询出符合条件的对象
Realm
的查询条件可以使用==、<=、<、>=、>、!=、BETWEEN、CONTAINS 以及 ENDSWITH等多种操作符
全量查询
1 2 3 4 5 6 7 8 9 10 11 12 13
| RLMResults *resArr = [Student allObjects]; NSLog(@"%@", resArr);
RLMRealm *realm = [RLMRealm defaultRealm]; Student *stu = [[Student alloc]initWithValue:@[@10, @"coder"]]; [realm transactionWithBlock:^{ [realm addObject:stu]; }];
NSLog(@"%@", resArr);
|
条件查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| RLMResults *stuArr = [Student objectsWhere:@"num > 7"]; NSLog(@"%@", stuArr);
RLMResults *stuArr2 = [stuArr sortedResultsUsingKeyPath:@"name" ascending:YES]; NSLog(@"%@", stuArr2);
RLMResults *stuArr3 = [stuArr2 objectsWhere:@"num > 8"];
RLMResults *stuArr4 = [stuArr3 objectsWhere:@"num > 9"]; NSLog(@"%@", stuArr4);
|
更新操作
- 需要修改的模型一定是被
Realm
所管理的模型, 而且已经和磁盘上的对象进行地址映射
- 对新添加的模型进行更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| RLMRealm *realm = [RLMRealm defaultRealm]; Student *stu4 = [[Student alloc]initWithValue:@{@"num": @4, @"name":@"titan4"}];
[realm transactionWithBlock:^{ [realm addObject:stu4]; }];
[realm transactionWithBlock:^{ stu4.name = @"coder4"; }];
|
1 2 3 4 5 6 7 8
| RLMResults *results = [Student objectsWhere:@"num = 4"]; Student *stu = results.firstObject;
[realm transactionWithBlock:^{ stu.name = @"titanking"; }];
|
- 当有主键的情况下, 使用
Update
方法
addOrUpdateObject
会去先查找有没有传入的Student
相同的主键,如果有,就更新该条数据
- 这里需要注意,
addOrUpdateObject
这个方法不是增量更新,所有的值都必须有,如果有哪几个值是null
,那么就会覆盖原来已经有的值,这样就会出现数据丢失的问题
createOrUpdateInRealm:withValue
这个方法是增量更新的,后面传一个字典,使用这个方法的前提是有主键
- 方法会先去主键里面找有没有字典里面传入的主键的记录,如果有,就只更新字典里面的子集;如果没有,就新建一条记录
1 2 3 4 5 6 7 8 9 10 11 12 13
| RLMRealm *realm = [RLMRealm defaultRealm]; Student *stu2 = [[Student alloc]initWithValue:@{@"num": @12, @"name":@"titan"}];
[realm transactionWithBlock:^{ [realm addOrUpdateObject:stu2]; }];
[realm transactionWithBlock:^{ [Student createOrUpdateInRealm:realm withValue:@{@"num": @11, @"name":@"titan11"}]; }];
|
删除操作
- 删除的模型, 一定要求是被
realm
所管理的已经存在的模型
- 当需要在
Realm
中删除某些对象时,需要注意的是,该方法的执行需要在一个事务中进行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| RLMRealm *realm = [RLMRealm defaultRealm];
RLMResults *results = [Student objectsWhere:@"name = 'titanking'"]; Student *titan1 = results.firstObject;
[realm transactionWithBlock:^{ [realm deleteObject:titan1]; }];
RLMResults *results = [Student objectsWhere:@"name = 'coder'"]; for (Student *stu in results) { [realm transactionWithBlock:^{ [realm deleteObject:stu]; }]; }
[realm transactionWithBlock:^{ [realm deleteAllObjects]; }];
Student *res = [Student objectInRealm:realm forPrimaryKey:@4];
[realm transactionWithBlock:^{ [realm deleteObject:res]; }];
|
Realm
数据库机制
- 上面用到的获取
realm
对象的方式都是通过defaultRealm
来获取默认配置的realm
对象
- 当我们需要创建不同的
realm
表格时又该如何操作呢?
- 下面我们来看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| - (void)setDefaultRealmForUser:(NSString *)username { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:username] URLByAppendingPathExtension:@"realm"]; [RLMRealmConfiguration setDefaultConfiguration:config]; }
|
做好上述配置之后, 便可创建不同的数据库了
1 2 3 4 5
| [self setDefaultRealmForUser:@"zhangsan"];
RLMRealm *realm = [RLMRealm defaultRealm];
|
通知
Realm
实例将会在每次写入事务提交后,给其他线程上的Realm
实例发送通知
- 一般控制器如果想一直持有这个通知,就需要申请一个属性, 强引用该属性,
strong
持有这个通知
- 集合通知是异步触发的,首先它会在初始结果出现的时候触发,随后当某个写入事务改变了集合中的所有或者某个对象的时候,通知都会再次触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @property (nonatomic, strong) RLMNotificationToken *token;
- (void)setUp { [super setUp];
RLMRealm *realm = [RLMRealm defaultRealm]; self.token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) { NSLog(@"接收到变更通知--%@", notification); }];
[self.token stop]; }
- (void)testExample { NoticeModel *noticeM = [[NoticeModel alloc] initWithValue:@{@"num": @1, @"name": @"sz"}];
RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:noticeM]; }]; }
|
数据库迁移
- 数据库存储方面的增删改查应该都没有什么大问题,比较蛋疼的应该就是数据迁移了
- 在版本迭代过程中,很可能会发生表的新增,删除,或者表结构的变化,如果新版本中不做数据迁移,用户升级到新版,很可能就直接crash了
- 数据迁移一直是困扰各类型数据库的一大问题, 但是对于
Realm
来说, 却方便很多, 这也是Realm
的优点之一
- 新增删除表,
Realm
不需要做迁移
- 新增删除字段,
Realm
不需要做迁移; Realm
会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
int newVersion = 4; config.schemaVersion = newVersion;
[config setMigrationBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion){ if (oldSchemaVersion < newVersion) {
NSLog(@"数据结构会自动迁移");
[migration enumerateObjects:@"DataMigration" block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { if (oldSchemaVersion < 1) { newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; } if (oldSchemaVersion < 2) { newObject[@"email"] = @""; } if (oldSchemaVersion < 3) { [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"]; } }]; } }];
[RLMRealmConfiguration setDefaultConfiguration:config];
[RLMRealm defaultRealm]; return YES; }
|
- 以上就是我最近学习到的关于
Realm
的部分内容, 文章不全, 有兴趣的可以参考下面的文章继续学习
- 参考文档