Realm
参考:
Realm数据库 从入门到“放弃”
https://www.jianshu.com/p/50e0efb66bdf
官网
https://realm.io/docs/swift/latest
https://realm.io/cn/docs/objc/latest/
Realm在iOS中的简单使用
https://www.jianshu.com/p/f415d07bc446
Realm使用深入详解篇1
https://www.jianshu.com/p/90f79201b309
Realm数据库 从入门到“放弃”
1
2012年起,后支持的平台包括Java,Objective-C,Swift,React Native,
Xamarin。
优点
跨平台
简单易用:Core Data 和 SQLite 冗余、繁杂的知识和代码
可视化:Realm Browser 这个工具
安装
纯OC项目,就安装OC的Realm
pod 'Realm'
纯Swift项目,就安装Swift的Realm
pod 'RealmSwift'
混编项目,就需要安装OC的Realm,然后要把 Swift/RLMSupport.swift 文件
一同编译进去
术语
RLMRealm
defaultRealm( )如同Core Data的管理对象上下文(managed object
context)一样
RLMObject
model要继承于RLMObject
RLMResults
查询结果RLMResults和NSArray类似,包含了一系列的RLMObject对象,包括排
序、查找等等操作。
属性
1. 创建数据库
代码:
- (void)creatDataBaseWithName:(NSString *)databaseName {
NSArray *docPath =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *path = [docPath objectAtIndex:0];
NSString *filePath = [path
stringByAppendingPathComponent:databaseName];
NSLog(@"数据库目录 = %@",filePath);
RLMRealmConfiguration *config = [RLMRealmConfiguration
defaultConfiguration];
config.fileURL = [NSURL URLWithString:filePath];
config.objectClasses = @[MyClass.class, MyOtherClass.class];
config.readOnly = NO;
int currentVersion = 1.0; //当前版本号
config.schemaVersion = currentVersion;
config.migrationBlock = ^(RLMMigration *migration , uint64_t
oldSchemaVersion) {
// 这里是设置数据迁移的block
if (oldSchemaVersion < currentVersion) {
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
}
内存数据库
通常情况下,Realm 数据库是存储在硬盘中的,但是您能够通过设置
inMemoryIdentifier而不是设置RLMRealmConfiguration中的 fileURL属性,以
创建一个完全在内存中运行的数据库。
RLMRealmConfiguration *config = [RLMRealmConfiguration
defaultConfiguration];
config.inMemoryIdentifier = @"MyInMemoryRealm";
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
1.内存数据库会在临时文件夹中创建多个文件,用来协调处理诸如跨进程通知之
类的事务。 实际上没有任何的数据会被写入到这些文件当中,除非操作系统由于
内存过满,才会去把内存里面的数据存入到文件中
2.如果某个内存 Realm 数据库实例没有被引用,那么所有的数据就会被释放。所
以必须要在应用的生命周期内保持对Realm内存数据库的强引用,以避免数据丢
失
2. 建表
model.h
#import <Realm/Realm.h>
@interface RLMUser : RLMObject
//不要加上 如nonatomic, atomic, strong, copy, weak 等等
@property NSString *accid;
//用户注册id
@property NSInteger custId;
//姓名
@property NSString *custName;
//头像大图url
@property NSString *avatarBig;
@property RLMArray<Car> *cars; //一对多关系
//RLM_ARRAY_TYPE 使得类声明在底部生效,不然要写在上面
RLM_ARRAY_TYPE(RLMUser) // 定义RLMArray<RLMUser>
@interface Car : RLMObject
@property NSString *carName;
@property RLMUser *owner;
@end
RLM_ARRAY_TYPE(Car) // 定义RLMArray<Car>
@end
关于RLMObject的的关系
一对一
@property RLMUser *owner;
一对多
@property RLMArray<Car> *cars;
反向关系
@interface Car : RLMObject
@property NSString *carName;
@property (readonly) RLMLinkingObjects *owners; //指向RLMUser.class
@end
@implementation Car
+ (NSDictionary *)linkingObjectsProperties {
return @{
@"owners": [RLMPropertyDescriptor
descriptorWithClass:RLMUser.class propertyName:@"cars"],
};
}
@end
还可以给RLMObject设置主键primaryKey,默认值defaultPropertyValues,忽
略的属性ignoredProperties,必要属性requiredProperties,索引
indexedProperties。比较有用的是主键和索引。
@implementation Book
// 主键
+ (NSString *)primaryKey {
return @"ID";
}
//设置属性默认值
+ (NSDictionary *)defaultPropertyValues{
return @{@"carName":@"测试" };
}
//设置忽略属性,即不存到realm数据库中
+ (NSArray<NSString *> *)ignoredProperties {
return @[@"ID"];
}
//一般来说,属性为nil的话realm会抛出异常,但是如果实现了这个方法的话,就只
有name为nil会抛出异常,也就是说现在cover属性可以为空了
+ (NSArray *)requiredProperties {
return @[@"name"];
}
//设置索引,可以加快检索的速度
+ (NSArray *)indexedProperties {
return @[@"ID"];
}
@end
3.存储数据
新建对象
// (1) 创建一个Car对象,然后设置其属性
Car *car = [[Car alloc] init];
car.carName = @"Lamborghini";
// (2) 通过字典创建Car对象
Car *myOtherCar = [[Car alloc] initWithValue:@{@"name" : @"Rolls-
Royce"}];
// (3) 通过数组创建狗狗对象
Car *myThirdcar = [[Car alloc] initWithValue:@[@"BMW"]];
所有的必需属性都必须在对象添加到 Realm 前被赋值
增
1. 子线程异步写入数据,建议异步操作,同步会堵塞线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_
DEFAULT, 0), ^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject: Car];
}];
});
2. 同步写入,堵塞线程,默认不建议,除非需要
[realm beginWriteTransaction];
[realm addObject:Car];
[realm commitWriteTransaction];
删
同步删除:
[realm beginWriteTransaction];
// 1删除单条记录
[realm deleteObject:Car];
// 2删除多条记录
[realm deleteObjects:CarResult];
// 3删除所有记录
[realm deleteAllObjects];
[realm commitWriteTransaction];
改
增量更新
[Car createOrUpdateInRealm:realm withValue:@{@"id": @1, @"price":
@9000.0f}];
有主键。方法会先去主键里面找有没有字典里面传入的主键的记录,如果有,就
只更新字典里面的子集。如果没有,就新建一条记录
不会影响么有设置的键
非增量更新
[realm addOrUpdateObject:Car];
所有的值都必须有,如果有哪几个值是null,那么就会覆盖原来已经有的值,这
样就会出现数据丢失的问题
查
查询
查询条件可以使用==、<=、<、>=、>、!=、BETWEEN、CONTAINS 以及
ENDSWITH等多种操作符
1. Realm中所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,
只有当属性被访问时,才能够读取相应的数据
2.查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘
上的数据
3.查询结果被引用后,没有使用,依然不会实际去查询数据库
4. 一旦数据有更新,先前的查询结果无需重新执行
//从默认数据库查询所有的车, 继承于RLMObject,其实现部分方法
RLMResults<Car *> *cars = [Car allObjects]; //这行代码并没有真正去查询
结果,直到使用时
// 使用断言字符串查询
RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = '棕黄色'
AND name BEGINSWITH '大'"];
// 使用 NSPredicate 查询
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@
AND name BEGINSWITH %@",
@"棕黄色", @"大"];
RLMResults *results = [Dog objectsWithPredicate:pred];
// 排序名字以“大”开头的棕黄色狗狗
RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = '棕黄
色' AND name BEGINSWITH '大'"] sortedResultsUsingProperty:@"name"
ascending:YES];
链式查询
两种方法:
RLMResults<Car *> *Cars = [Car objectsWhere:@"color = blue"];
RLMResults<Car *> *CarsWithBNames = [Cars objectsWhere:@"name
BEGINSWITH 'B'"];
支持KVC和KVO
KVC : RLMObject、RLMResult以及 RLMArray 都遵守键值编码(Key-Value
Coding)(KVC)机制。当您在运行时才能决定哪个属性需要更新的时候,这个
方法是最有用的
RLMResults<Person *> *persons = [Person allObjects];
[[RLMRealm defaultRealm] transactionWithBlock:^{
//设置单个对象的值
[[persons firstObject] setValue:@YES forKeyPath:@"isFirst"];
//将每所有的 planet 属性设置为“地球”
[persons setValue:@"地球" forKeyPath:@"planet"];
}];
KVO : 所有 RLMObject子类的持久化(persisted)存储(未被忽略)的属性都是
遵循 KVO 机制的,并且 RLMObject以及 RLMArray中 无效的(invalidated)属性
也同样遵循(然而 RLMLinkingObjects属性并不能使用 KVO 进行观察)
数据库加密
Realm 支持在创建 Realm 数据库时采用64位的密钥对数据库文件进行
AES-256+SHA2 加密。这样硬盘上的数据都能都采用AES-256来进行加密和解
密,并用 SHA-2 HMAC 来进行验证。每次您要获取一个 Realm 实例时,您都
需要提供一次相同的密钥
加密过的 Realm 只会带来部分额外资源占用 ,大约10%
// 产生随机密钥
NSMutableData *key = [NSMutableData dataWithLength:64]; //64位
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t
*)key.mutableBytes);
// 打开加密文件
RLMRealmConfiguration *config = [RLMRealmConfiguration
defaultConfiguration];
config.encryptionKey = key;
NSError *error = nil;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config
error:&error];
if (!realm) {
// 如果密钥错误,`error` 会提示数据库不可访问
NSLog(@"Error opening realm: %@", error);
}
通知
// 获取 Realm 通知
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm
* realm) {
[myViewController updateUI];
}];
[token stop];
// 移除通知
[realm removeNotification:self.token];
一般控制器如果想一直持有这个通知,就需要申请一个属性,strong持有这个通
知
- (void)viewDidLoad {
[super viewDidLoad];
// 观察 RLMResults 通知
__weak typeof(self) weakSelf = self;
self.notificationToken = [[Person objectsWhere:@"age > 5"]
addNotificationBlock:^(RLMResults<Person *> *results,
RLMCollectionChange *change, NSError *error) {
if (error) {
NSLog(@"Failed to open Realm on background worker: %@",
error);
return;
}
UITableView *tableView = weakSelf.tableView;
// 对于变化信息来说,检索的初次运行将会传递 nil
if (!changes) {
[tableView reloadData];
return;
}
// 检索结果被改变,因此将它们应用到 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];
}];
}
集合通知
集合通知是异步触发的,首先它会在初始结果出现的时候触发,随后当某个写入
事务改变了集合中的所有或者某个对象的时候,通知都会再次触发。这些变化可
以通过传递到通知闭包当的 RLMCollectionChange参数访问到。这个对象当中
包含了受 deletions、insertions和 modifications 状态所影响的索引信息。
集合通知对于 RLMResults、RLMArray、RLMLinkingObjects 以及
RLMResults 这些衍生出来的集合来说,当关系中的对象被添加或者删除的时
候,一样也会触发这个状态变化
let resultToken = results.observe { (change:
RealmCollectionChange<Results<Animal>>) in
switch change {
case .initial(let results):
print("Observe Result initial: \(results.count)")
break
case .update(let results, let deletions, let insertions, let modifications):
print("Observe Result update: \(results.count) deletions => \
(deletions) insertions => \(insertions) modifications => \
(modifications)")
default:
print("Observe Result error")
}
}
1
1、Realm/Object/Results/List都可被观察,并且当数据发生变化不论是在哪个
进程或者线程都会被通知到
2、所有的观察不可以在Realm的实例事务中操作,不管被管理对象是否归属于
当前Realm实例managed
3、相同线程下的不同Realm实例事务操作不能够嵌套,因为这个时候即使新建
Realm实例,但其还是处于transition中,不同线程下的不同Realm实例是可以
嵌套的
数据库迁移
1.新增删除表,Realm不需要做迁移
2.新增删除字段,Realm不需要做迁移。Realm 会自行检测新增和需要移除的属
性,然后自动更新硬盘上的数据库架构
3.迁移
RLMRealmConfiguration *config = [RLMRealmConfiguration
defaultConfiguration];
config.schemaVersion = 2; //设置版本号,版本迁移和升级修改
config.migrationBlock = ^(RLMMigration *migration, uint64_t
oldSchemaVersion)
{
// enumerateObjects:block: 遍历了存储在 Realm 文件中的每一
个“Person”对象
[migration enumerateObjects:Person.className block:^(RLMObject
*oldObject, RLMObject *newObject) {
//1. 合并字段 firstName,lastName -> fullName: 只有当 Realm 数
据库的架构版本为 0 的时候,才添加 “fullName” 属性
if (oldSchemaVersion < 1) {
newObject[@"fullName"] = [NSString stringWithFormat:@"%@
%@", oldObject[@"firstName"], oldObject[@"lastName"]];
}
//2.增加新字段 email : 只有当 Realm 数据库的架构版本为 0 或者 1 的
时候,才添加“email”属性
if (oldSchemaVersion < 2) {
newObject[@"email"] = @""; //默认赋值“”
}
// 3.原字段重命名 : 替换属性名
if (oldSchemaVersion < 3) { // 重命名操作应该在调用
`enumerateObjects:` 之外完成
[migration renamePropertyForClass:Person.className
oldName:@"yearsSinceBirth" newName:@"age"]; }
}];
};
[RLMRealmConfiguration setDefaultConfiguration:config];
// 现在我们已经成功更新了架构版本并且提供了迁移闭包,打开旧有的 Realm
数据库会自动执行此数据迁移,然后成功进行访问
[RLMRealm defaultRealm];
多线程操作
1. Realm实例对象不可以跨线程访问
let realm = try! Realm()
print("Thread isMain: \(Thread.isMainThread)")
/// 这里我们验证一个打开的realm是否可以跨线程
/// 错误示例,Realm实例不能够跨线程
DispatchQueue.global().async {
let student = Student()
try? realm.write {
realm.add(student, update: true)
}
}
2. Object子类也 不可以跨线程访问
let student = Student()
try? realm.write {
realm.add(student, update: true)
}
/// 错误示例,已经被Realm示例managed的对象不能够跨线程
/// 但是处于unmanaged的对象就可以当成一般的对象使用,是可以跨线程访
问的,可以尝试将上述add屏蔽掉再来看看结果
DispatchQueue.global().async {
let realm = try! Realm()
try? realm.write {
realm.add(student, update: true)
}
}
3. Results 查询结果 也不可以跨线程访问
let results = realm.objects(Student.self)
DispatchQueue.global().async {
print("Thread Results access before")
print("Thread Results access \(results.count)") // 这里会出错
print("Thread Results access after")
}
如何跨线程访问 ?
使用 : ThreadSafeReference
4. 针对Object跨线程访问
let studentRef = ThreadSafeReference<Student>(to: student)
DispatchQueue.global().async {
let realm = try! Realm()
guard let studentCopy = realm.resolve(studentRef) else {
return
}
// 可以看到结果正常输出
print("Thread Object ThreadSafeReference: \(studentCopy.idCard)")
}
5. 针对Results跨线程访问
let resultsRef = ThreadSafeReference<Results<Student>>(to: results)
DispatchQueue.global().async {
let realm = try! Realm()
guard let resultsCopy = realm.resolve(resultsRef) else {
return
}
// 可以看到结果正常输出
print("Thread Results ThreadSafeReference: \(resultsCopy.count)")
// 那么我们是否可以进一步访问resultsCopy中的结果呢? 从输出结果看,
答案是可以的
print("Thread Results ThreadSafeReference Object update before: \
(resultsCopy.first)")
try? realm.write {
resultsCopy.first?.name = "ThreadSafeReference"
}
print("Thread Results ThreadSafeReference Object update after: \
(resultsCopy.first)")
}
总结:
1、Realm、Object、Results 或者 List 被管理实例皆受到线程的限制,只能够
在被创建且被管理的实例线程中使用
2、Object、Results、List也可以通过ThreadSafeReference来跨线程安全访
问,这意味着当我们不确定某个被管理对象或者已经确定某个被管理对象在其他
线程使用,开始新线程访问前我们都都可以通过线程安全引用使其能够跨线程访
问
3、如果一个ThreadSafeReference被一个Realm实例resolve后,那么
ThreadSafeReference所指向的那个对象的管理Realm也将会变更到当前实例
2
1、在进行任何已被管理的对象操作时都必须满足 [Object].isInvalidated ==
false
2、相同线程下的不同Realm实例事务操作不能够嵌套,因为这个时候即使新建
Realm实例,但其还是处于transition中,不同线程下的不同Realm实例是可以
嵌套的
3、Realm不支持自增key
4、Realm查询到Results仅当真正访问的时候才会加载到内存当中,故Realm查
询并不支持limit,并且对数据加载到内存更友好
5、Realm查询结果是自更新的,亦即意味着在任意线程更新了数据,那么都将
会自动更新到查询结果
3
1.类名称的长度最大只能存储 57 个 UTF8 字符。
2.属性名称的长度最大只能支持 63 个 UTF8 字符。
3.NSData以及 NSString属性不能保存超过 16 MB 大小的数据。如果要存储大量
的数据,可通过将其分解为16MB 大小的块,或者直接存储在文件系统中,然后
将文件路径存储在 Realm 中。如果您的应用试图存储一个大于 16MB 的单一属
性,系统将在运行时抛出异常。
4.对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符
补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在
0~591 之间)。
6.Realm对象的 Setters & Getters 不能被重载
因为 Realm 在底层数据库中重写了 setters 和 getters 方法,所以您不可以在您
的对象上再对其进行重写。一个简单的替代方法就是:创建一个新的 Realm 忽
略属性,该属性的访问起可以被重写, 并且可以调用其他的 getter 和 setter 方
法。
7.文件大小 & 版本跟踪
一般来说 Realm 数据库比 SQLite 数据库在硬盘上占用的空间更少。如果您的
Realm 文件大小超出了您的想象,这可能是因为您数据库中的 RLMRealm中包
含了旧版本数据。
为了使您的数据有相同的显示方式,Realm 只在循环迭代开始的时候才更新数据
版本。这意味着,如果您从 Realm 读取了一些数据并进行了在一个锁定的线程
中进行长时间的运行,然后在其他线程进行读写 Realm 数据库的话,那么版本
将不会被更新,Realm 将保存中间版本的数据,但是这些数据已经没有用了,这
导致了文件大小的增长。这部分空间会在下次写入操作时被重复利用。这些操作
可以通过调用writeCopyToPath:error:来实现。
解决办法:
通过调用invalidate,来告诉 Realm 您不再需要那些拷贝到 Realm 的数据了。
这可以使我们不必跟踪这些对象的中间版本。在下次出现新版本时,再进行版本
更新。
您可能在 Realm 使用Grand Central Dispatch时也发现了这个问题。在
dispatch 结束后自动释放调度队列(dispatch queue)时,调度队列
(dispatch queue)没有随着程序释放。这造成了直到
RLMRealm 对象被释放后,Realm 中间版本的数据空间才会被再利用。为了避
免这个问题,您应该在 dispatch 队列中,使用一个显式的自动调度队列
(dispatch queue)。
8.Realm 没有自动增长属性
Realm 没有线程/进程安全的自动增长属性机制,这在其他数据库中常常用来产
生主键。然而,在绝大多数情况下,对于主键来说,我们需要的是一个唯一的、
自动生成的值,因此没有必要使用顺序的、连续的、整数的 ID 作为主键。
解决办法:
在这种情况下,一个独一无二的字符串主键通常就能满足需求了。一个常见的模
式是将默认的属性值设置为 [[NSUUID UUID] UUIDString]
以产生一个唯一的字符串 ID。
自动增长属性另一种常见的动机是为了维持插入之后的顺序。在某些情况下,这
可以通过向某个 RLMArray中添加对象,或者使用 [NSDate date]默认值的
createdAt属性。
9.所有的数据模型必须直接继承自RealmObject。这阻碍我们利用数据模型中的
任意类型的继承。
这一点也不算问题,我们只要自己在建立一个model就可以解决这个问题。自己
建立的model可以自己随意去继承,这个model专门用来接收网络数据,然后把
自己的这个model转换成要存储到表里面的model,即RLMObject对象。这样这
个问题也可以解决了。
Realm 允许模型能够生成更多的子类,也允许跨模型进行代码复用,但是由于某
些 Cocoa 特性使得运行时中丰富的类多态无法使用。以下是可以完成的操作:
父类中的类方法,实例方法和属性可以被它的子类所继承
子类中可以在方法以及函数中使用父类作为参数
以下是不能完成的:
多态类之间的转换(例如子类转换成子类,子类转换成父类,父类转换成子类
等)
同时对多个类进行检索
多类容器 (RLMArray以及 RLMResults)
10.Realm不支持集合类型
这一点也是比较蛋疼。
Realm支持以下的属性类型:BOOL、bool、int、NSInteger、long、long
long、float、double、NSString、NSDate、NSData以及 被特殊类型标记的
NSNumber。CGFloat属性的支持被取消了,因为它不具备平台独立性。
这里就是不支持集合,比如说NSArray,NSMutableArray,NSDictionary,
NSMutableDictionary,NSSet,NSMutableSet。如果服务器传来的一个字典,
key是一个字符串,对应的value就是一个数组,这时候就想存储这个数组就比较
困难了。当然Realm里面是有集合的,就是RLMArray,这里面装的都是
RLMObject。
所以我们想解决这个问题,就需要把数据里面的东西都取出来,如果是model,
就先自己接收一下,然后转换成RLMObject的model,再存储到RLMArray里面
去,这样转换一遍,还是可以的做到的。
使用
常用基本操作
/// 初始化一个Realm实例,将采用默认配置
var realm = try! Realm()
/// add 添加/更新一个对象
let student = Student()
student.idCard = "20191120"
try? realm.write {
// 当update=true时,必须要求Object的subclass实现override static func
primaryKey() -> String
realm.add(student, update: true)
}
// or
realm.beginWrite()
realm.add(student, update: true)
try? realm.commitWrite()
/// 查询
let results = realm.objects(Student.self)
print("Basisc query results: \(results.count)")
// 还可以进行链式操作
let results0 = results.filter("idCard=%@", "20191120")
// 如果设置了主键还可以查询指定的对象
let queryStudent = realm.object(ofType: Student.self, forPrimaryKey:
"20191120")
print("Basisc query object: \(queryStudent)")
/// 删除一个对象或者一系列对象
let student1 = Student()
student1.idCard = "20191121"
try? realm.write {
realm.add(student1, update: true)
}
print("Basisc delete before results count: \(results.count)")
let results1 = realm.objects(Student.self).filter("idCard=%@",
"20191121")
try? realm.write {
realm.delete(results1)
}
print("Basisc delete after results count: \(results.count)") // 可以看出
results结果实现了自更新
// 当然你也可以
//try? realm.write {
// realm.delete(student1)
//}
/// 多对多的使用,多对一或一对一
/// 请看Student的:books属性
let book = Book()
book.id = 1
book.name = "语文"
let book1 = Book()
book1.id = 2
book.name = "数学"
try? realm.write {
realm.add(book, update: true)
realm.add(book1, update: true)
student.books.append(book)
student.books.append(book1)
}
print("Basisc Many-to-Many Many-to-One: \(student.books)")
/// inverse 关系,什么时候我们会用到这种反转的关系呢?当一个东西拥有多
个关联持有者是,我们希望知道他的持有者是谁,那么这个时候就有必要
/// 请看Book的:owners 属性
DispatchQueue.global().async {
let book = (try! Realm()).object(ofType: Book.self, forPrimaryKey: "1")
print("Basisc Many-to-Many Many-to-One inverse: \
(book.owners.first)")
}
realm原理
Realm 是一个 MVCC 数据库 ,底层是用 C++ 编写的。MVCC 指的是多版本并
发控制
可以把 Realm 的内部想象成一个 Git,它也有分支和原子化的提交操作
Realm是满足ACID的。原子性(Atomicity)、一致性(Consistency)、隔离
性(Isolation)、持久性(Durability)。一个支持事务(Transaction)的数据
库,必需要具有这四种特性。Realm都已经满足
传统加锁缺点 :
在写操作完成之前,所有的读操作都会被阻塞。这就是众所周知的读-写锁。这
常常都会很慢
MVCC并非优点:
无需加锁,像Git一样支持备份,一旦成功后才会修改数据
Realm底层是B+树实现的
Realm会让每一个连接的线程都会有数据在一个特定时刻的快照。这也是为什么
能够在上百个线程中做大量的操作并同时访问数据库,却不会发生崩溃的原因
左图是一个数据插入过程:
第二个阶段保持V1不动, 关联上新的数据V2,如果不成功,删除新增,不影响
正常数据,
如果成了,V2变成正式数据
整个过程完全不需要加锁
传统的数据库操作 :
一系列的SQL语句 -> 创建一个数据库连接 -> 服务器收到请求 -> SQL语句进
行词法和语法语义分析 -> SQL语句进行优化 -> 查询 -> 读取磁盘的数据(有
索引则先读索引) -> 存到内存里(这里有内存消耗)-> 把数据序列化成可在内
存里面存储的格式 -> 转换成语言层面的类型 -> 以对象的形式返回(oc等)
Realm的数据库查询 :
正是Realm采用了 zero-copy 架构,几乎没有内存开销,Realm核心文件格式基
于memory-mapped,节约了大量的序列化和反序列化的开销,导致了Realm获
取对象的速度特别高效
Realm的数据库文件是通过memory-mapped,也就是说数据库文件本身是映射
到内存(实际上是虚拟内存)中的,Realm访问文件偏移就好比文件已经在内存中
一样(这里的内存是指虚拟内存),它允许文件在没有做反序列化的情况下直接从
内存读取,提高了读取效率。Realm 只需要简单地计算偏移来找到文件中的数
据,然后从原始访问点返回数据结构的值 。
默认不能跨线程访问,这样也是查询效率更高的原因之一,
Database File
.realm 文件是memory mapped的,所有的对象都是文件首地址偏移量的一个
引用。对象的存储不一定是连续的,但是Array可以保证是连续存储。
.realm执行写操作的时候,有3个指针,一个是*current top pointer ,一个是
other top pointer ,最后一个是 switch bit*。
.lock文件中会包含 the shared group 的metadata。这个文件承担着允许多线
程访问相同的Realm对象的职责
Commit logs history
这个文件会用来更新索引indexes,会用来同步。里面主要维护了3个小文件,2
个是数据相关的,1个是操作management的。