在raywenderlick上面看到一篇介绍Realm的文章,试用了一下,确实比CoreData方便很多,固然使用Realm会加大app的体积,官方文档上说的是Realm库文件大小在1M左右。这篇笔记对Realm的使用作一个简单的总结,raywenderlick上面的文章我也用Objective-C从新实现了一遍。ios
Realm是一个移动端数据库,专门针对移动APP设计,不只适用于iOS,也适用于Android,目前最新版本是1.0.2,我这用的是0.9.8版本。它底层并不依赖SQLite,有本身的一套存储引擎。官网地址https://realm.io/cn/,有很是详细的中文文档,也支持CocoaPods,因此在项目中只要在Podfile中加入Realm而后 pod install
就行。如下代码描述都是针对iOS,其余语言请参照官方文档。git
Realm是一个类MVCC数据库,每一个链接的线程在特定的时刻都有一个数据库的快照。MVCC在设计上采用了和Git同样的源文件管理算法,也就是说你的每一个链接线程就比如在一个分支(也就是数据库的快照)上工做,可是你并无获得一个完整的数据库拷贝。Realm和一些真正的MVCC数据库如MySQL是不一样的,Real在某个时刻只能有一个写操做,且老是操做最新的数据版本,不能在老版本操做。github
Realm数据库使用了零拷贝技术,这是与CoreData及其余数据库彻底不一样的地方。web
一般的数据库操做是这样的,数据存储在磁盘的数据库文件中,咱们的查询请求会转换为一系列的SQL语句,建立一个数据库链接。数据库服务器收到请求,经过解析器对SQL语句进行词法和语法语义分析,而后经过查询优化器对SQL语句进行优化,优化完成执行对应的查询,读取磁盘的数据库文件(有索引则先读索引),返回对应的数据内容并存储到内存中,数据还须要序列化成内存可存储的格式,最后数据还要转换成语言层面的类型,好比Objective-C的对象等。算法
而Realm彻底不一样,它的数据库文件是经过memory-mapped
,也就是说数据库文件自己是映射到内存中的,Realm访问文件偏移就比如文件已经在内存中同样(这里的内存是指虚拟内存),它容许文件在没有作反序列化的状况下直接从内存读取,提升了读取效率。数据库
零拷贝架构也使得Realm能够自动更新对象和查询。在一个查询中更新对象,在另一个查询中能够立刻读取到更新的内容。多线程同时更新数据也是同样,能够即时更新对象的内容。正是由于对象的自动更新,因此Realm中也是不容许多线程之间的对象共享,由于若是多线程共享Realm对象,会致使数据的不一致性,虽然经过加锁是能够保证数据一致性的,可是会增长开销。服务器
所以,在使用Realm的时候,不要在多个线程之间共享对象。若是要在另一个线程获取一样的数据,请从新执行查询。 多线程更新数据的操做后面会有例子演示。多线程
Realm Objective-C支持的数据类型有BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData 以及 被特殊类型标记的 NSNumber
,注意:Realm不支持auto_increment类型。Realm中涉及的几个类和概念以下:架构
这是Realm数据库框架的核心,它是一个访问底层数据库的指针,有点相似CoreData中的ManagedObjectContext对象。在代码中能够经过 [RLMRealm defaultRealm]
获取。app
这是Realm的对象模型。本身定义的对象要继承该类,而后能够定义本身的属性。也能够定义主键(覆写 + (NSString *)primaryKey
方法)和索引(覆写+ (NSArray<NSString *> *)indexedProperties
方法)等。
多对一关系或一对一关系
对于多对一或者一对一的关系,只须要声明一个RLMObject子类类型的属性便可。你能够简单的经过这个属性实现关系的绑定。
``` //多对一或者一对一关系代码示例 // Dog.h @interface Dog : RLMObject // 其他属性声明... @property Person *owner; @end //绑定代码 Person *jim = [[Person alloc] init]; Dog *rex = [[Dog alloc] init]; rex.owner = jim; ```
一对多关系
对于一对多的关系,则须要声明一个RLMArray类型的属性。另外,还要在一的类中加入一个协议声明。RLM_ARRAY_TYPE 宏建立了一个协议,从而容许 RLMArray<Dog> 语法的使用。
//Dog.h @interface Dog : RLMObject // 属性声明... @end RLM_ARRAY_TYPE(Dog) // 定义一个 RLMArray<Dog>类型 // Person.h @interface Person : RLMObject // 其他的属性声明... @property RLMArray<Dog *><Dog> *dogs; @end //绑定代码 RLMResults<Dog *> *someDogs = [Dog allObject]; [jim.dogs addObjects:someDogs]; [jim.dogs addObject:rex];
反向关系
在前面的例子中,加入dog的时候,咱们只是绑定了一我的能够有多只狗,可是并无指定这只狗的主人是谁。为了自动加入狗与人的绑定关系,须要用反向关系来解决,在Dog.h中声明一个RLMLinkingObjects对象,而后在实现代码中加入反向关系的连接函数,这样,当咱们在加入dogs的时候,会自动设置好这只狗的主人(owners)属性。
@interface Dog : RLMObject @property NSString *name; @property NSInteger age; @property (readonly) RLMLinkingObjects *owners; @end @implementation Dog + (NSDictionary *)linkingObjectsProperties { return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"dogs"], }; } @end
Realm中全部涉及数据更改的操做如insert,delete,update等,必须在一个write事务中执行。
[[RLMRealm defaultRealm] transactionWithBlock:^{ ...... }];
查询操做很简单,不须要在一个write事务中执行。Realm全部的查询操做都是延迟加载的,只有当属性被访问的时候,才会读取相应的数据。查询结果并非数据的拷贝,修改查询结果(在写入事务中)会直接修改硬盘上的数据。
查询对象的基本方法是 RLMObject
的allObjects
方法,查询的结果是一个RLMResults<RLMObject *>
对象。还能够条件查询并对结果排序,代码以下:
RLMResults<Dog *> *dogs = [Dog allObjects]; // 从默认的 Realm 数据库中,检索全部狗狗 // 使用断言字符串查询 RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = '白色' AND name BEGINSWITH '小'"]; // 使用 NSPredicate 查询 NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@", @"白色", @"小"]; tanDogs = [Dog objectsWithPredicate:pred]; // 排序名字以“大”开头的黑色狗狗 RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = '黑色' AND name BEGINSWITH '大'"] sortedResultsUsingProperty:@"name" ascending:YES];
与其余数据库不一样的是,Realm并无提供LIMIT
之类的关键字来限制一次加载的数据量。由于Realm中的查询是延迟加载的,只有在查询结果被使用到的时候,才会读取数据库文件去加载对象,因此并不用LIMIT
来实现分页。
当咱们须要修改数据模型时,好比增减属性,属性重命名等,都进行数据迁移。后面的一节会有实例介绍。
首先建立一个Podfile,下载Realm库。为了查看Realm数据库的数据,推荐下载一个官方的工具Realm Browser,经过这个工具咱们只要到对应数据库的目录下,点击default.realm就能够看到数据库的全部数据了。
#Podfile platform :ios, ‘9.0’ use_frameworks! target ‘RealmStart’ do pod 'Realm', '~> 0.98' end
数据库文件的目录能够经过[RLMRealm defaultRealm].configuration.fileURL)
获得,而后在文件管理器经过前往对应目录就能够了(注:Mac默认隐藏了用户目录的Library目录,能够经过菜单栏的前往
或者快捷键Command+Shift+g
打开前往对话框。数据库文件默认是没有加密的,若是须要加密,能够参照官方文档-加密这一节内容。另外,Realm还提供了Xcode插件,能够去官网下载并安装,方便创建数据模型。
我这里新建了三个类,分布是Dog,Person以及Company。其中Dog和Person是多对多的关系,即一只狗能够属于多我的,而一我的能够有多只狗。Person和Company为多对一的关系,即一我的只能属于一个公司,一个公司能够有不少人。
代码以下:
//Dog.h#import <Realm/Realm.h> @interface Dog : RLMObject @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *color; @property (nonatomic, readonly) RLMLinkingObjects *owners; @end RLM_ARRAY_TYPE(Dog) //必须加这个宏定义 //Dog.m #import "Dog.h" #import "Person.h" @implementation Dog //反向连接 + (NSDictionary *)linkingObjectsProperties { return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"dogs"], }; } @end //Person.h #import <Realm/Realm.h> #import "Company.h" #import "Dog.h" @interface Person : RLMObject @property (nonatomic, strong) NSString *name; @property (nonatomic) NSInteger age; @property (nonatomic, strong) Company *company; @property (nonatomic, strong) RLMArray<Dog *><Dog> *dogs; //一对多 @end //Person.m #import "Person.h" @implementation Person //为属性name加索引 + (NSArray<NSString *> *)indexedProperties { return @[@"name"]; } @end //Company.h #import <Realm/Realm.h> @interface Company : RLMObject @property (nonatomic, strong) NSString *name; @end //Company.m #import "Company.h" @implementation Company @end
而后在AppDelegate.m中加入测试代码以下:
/** 清理数据库文件,为测试环境作准备。 */ - (void)cleanRealm { 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) { NSLog(@"clean realm error:%@", error); } } } /** 添加测试数据 */ - (void)addInitDataToRealm { Company *company = [[Company alloc] init]; company.name = @"GOOGLE"; Person *person = [[Person alloc] init]; person.name = @"张三"; person.age = 28; person.company = company; Dog *dog1 = [[Dog alloc] init]; dog1.name = @"小黑"; dog1.color = @"黑色"; Dog *dog2 = [[Dog alloc] init]; dog2.name = @"小狗子"; dog2.color = @"黑色"; Dog *dog3 = [[Dog alloc] init]; dog3.name = @"大白"; dog3.color = @"白色"; [person.dogs addObject:dog1]; [person.dogs addObject:dog2]; [person.dogs addObject:dog3]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:person]; }]; } /** 查询测试 */ - (void)queryRealm { RLMResults<Dog *> *dogs = [[Dog objectsWhere:@"color = '黑色' AND name BEGINSWITH '小'"] sortedResultsUsingProperty:@"name" ascending:YES]; for (Dog *dog in dogs) { NSLog(@"dog:%@, owners:%@", dog, dog.owners); } } /** 更新测试 */ - (void)updateRealm { NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@", @"白色", @"大"]; RLMResults *dogs = [Dog objectsWithPredicate:pred]; [[RLMRealm defaultRealm] transactionWithBlock:^{ for (Dog *dog in dogs) { dog.color = @"新的颜色"; } }]; } /** 多线程测试 */ - (void)multithreadRealm { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); NSLog(@"start async"); RLMResults *results = [Person objectsWhere:@"name = '张三' "]; if (results.count > 0) { Person *person = results[0]; NSLog(@"outer block, name:%@", person.name); } [[RLMRealm defaultRealm] transactionWithBlock:^{ NSLog(@"in async block"); RLMResults *results = [Person objectsWhere:@"name = '张三' "]; if (results.count > 0) { Person *person = results[0]; person.name = @"王麻子"; NSLog(@"change name to wangmazi"); } }]; if (results.count > 0) { Person *person = results[0]; NSLog(@"async person:%@, tid=%@", person.name, [NSThread currentThread]); } }); NSArray *names = @[@"张三", @"李四"]; [[RLMRealm defaultRealm] transactionWithBlock:^{ int i = 0; while (i < 2) { NSString *name = names[i]; RLMResults *results = [Person objectsWhere:@"name = %@", name]; if (results.count > 0) { Person *person = results[0]; if ([person.name isEqualToString:@"李四"]) { person.name = @"王五"; NSLog(@"change name to wangwu"); } else { person.name = @"李四"; NSLog(@"change name to lisi"); } sleep(3); } i++; } }]; } /** 多线程输出: 2016-07-16 20:34:31.103 RealmStart[32013:1565410] change name to lisi 2016-07-16 20:34:32.104 RealmStart[32013:1565455] start async 2016-07-16 20:34:32.108 RealmStart[32013:1565455] outer block, name:张三 2016-07-16 20:34:34.172 RealmStart[32013:1565410] change name to wangwu 2016-07-16 20:34:37.248 RealmStart[32013:1565455] in async block */
若是咱们要修改数据模型,好比Company增长一个属性age,这个时候就须要迁移数据。那么除了在Company.h中加入属性声明外,还要在app开始加入迁移代码,设置数据库版本。迁移数据的代码很简单,以下,每次修改数据模型都要修改版本号。若是是增长索引,测试发现能够不迁移数据,而若是是更改属性名字,则须要加入迁移代码保证新旧属性的数据迁移,更多用法能够参加官网文档。
- (void)migrateRealm { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion){ if (oldSchemaVersion < 1) { //什么都不作 } }; [RLMRealmConfiguration setDefaultConfiguration:config]; [RLMRealm defaultRealm]; }
[realm beginWriteTransaction]
和[realm commitWriteTransaction]
)。本文示例的代码地址:
https://github.com/shishujuan/ios_study/tree/master/realm/RealmStart
参考资料3的Objective-C实现的代码地址:
https://github.com/shishujuan/ios_study/tree/master/realm/RealmDemo