从FMDB到WCDB、微信团队怎么说?
WCDB背景
自己初次见到WCDB是微信开发团队公众号在今年五月份推送的一篇文章中(开发者团队的微信号上面图片中有,值得大家关注一下),那时候就说在筹备着WCDB的开源,觉得很是新奇,在两个多月前WCDB开源了!自己是最近才有时间看的WCDB,总结一下自己的理解和学习的东西,WCDB是微信团队开源的支持Android,也支持iOS,那当然也是会支持macOS的一个移动端数据库框架,FMDB估计做iOS的99.99%的都知道,就像Android开发中使用LitePal一样,都是在SQLite的基础上封装的移动数据库框架,WCDB是微信团队提供一个高效、易用、完整的移动端存储方案。 它包含三个模块:
1、WCDB-iOS/Mac
2、WCDB-Android
3、数据库损坏修复工具WCDBRepair
iOS 数据库框架对比分析
一:关系型数据库,代表有CoreData、FMDB等
CoreData:微信团队在公众号的文章中对它的总结是这样:它是苹果内建框架,和Xcode深度结合,可以很方便进行ORM;但其上手学习成本较高,不容易掌握。稳定性也堪忧,很容易crash;多线程的支持也比较鸡肋。
FMDB:它基于SQLite封装,对于有SQLite和ObjC基础的开发者来说,简单易懂,可以直接上手;而缺点也正是在此,FMDB只是将SQLite的C接口封装成了ObjC接口,没有做太多别的优化,即所谓的胶水代码(Glue Code)。使用过程需要用大量的代码拼接SQL、拼装Object,并不方便
二:key-value数据库,代表有Realm、LevelDB、RocksDB等
微信团队对上面的总结是这样:因其在各平台封装、优化的优势,比较受移动开发者的欢迎。对于iOS开发者,key-value的实现直接易懂,可以像使用NSDictionary一样使用Realm。并且ORM彻底,省去了拼装Object的过程。但其对代码侵入性很强,Realm要求类继承RLMObject的基类。这对于单继承的ObjC,意味着不能再继承其他自定义的子类。同时,key-value数据库对较为复杂的查询场景也比较无力。
说说自己的理解:上面的像Realm、LevelDB、RocksDB等key - value 类型的这几个框架我都没有使用过,没有什么话语权,说说自己用过的,上面的CoreData和FMDB,我记得我去年有写过一篇博客,就这两者之间的区别等等的做过总结,有兴趣的可以去翻翻以前的,我也记得唐巧哥以前在他的公众号文章中也说过这事,就这两者之间还是支持FMDB,当然我相信CoreData苹果说不定哪天就让它变得受人们青睐,但当前可能还是做得不够吧,所以你这样看可能也就不难理解,一起为什么那么多人用FMDB,但确实也是有些场景中CoreData能做起来容易点的的不一定FMDB也容易,比如在两张表之间建立联系的时候,CoreData就会相对容易一点,所以,就像微信团队最后总结那那句一样:各个方案都有其独特的优势及劣势,没有最好的,只有最适合的。
你期盼的数据库框架是什么样子的?
下面这一段内容我不知道有多少伙伴在微信开发团队的公众号当中看到过,我自己看完下面这段话的时候,觉得总结的每一句话都是开发者的心声,也许看完这段话你也会和我一样,更加期盼的想去看看WCDB:
-
高效;增删改查的高效是数据库最基本的要求。除此之外,我们还希望能够支持多个线程高并发地操作数据库,以应对微信频繁收发消息的场景。
-
易用;这是微信开源的原则,也是WCDB的原则。SQLite本不是一个易用的组件:为了完成一个查询,往往我们需要写很多拼接字符串、组装Object的胶水代码。这些代码冗长繁杂,而且容易出错,我们希望组件能统一完成这些任务。
-
完整;数据库操作是一个复杂的场景,我们希望数据库组件能完整覆盖各种场景。包括数据库损坏、监控统计、复杂的查询、反注入等。
初试WCDB- 理解ORM
下面的内容就从最基本的开始,从表的创建,到后面的CRUD的操作,以及再到后面一些高级的用法全都过一遍,在这当中涉及到的问题,有些可能会给连接大家可以自己去学习理解,有些我会说书我自己的理解,WCDB我们上路......
安装WCDB,在Wiki里面说的也比较完整,这里我们就不在多说,直接使用CocoaPods直接安装即可。
想理解WCDB需要先理解最基本的这个概念 ORM ,大家可以点进去看看微信给的使用说明,我们接着说:
在我们的Demo中,我们创建一个Message类,然后在这个类中声明我们需要的一些属性:
上面文件大家看到了这个Message+WCTTableCoding.h ,看着很像是我们常用的类别,其实就是,下面会说它的创建和作用,我们在我们的Message类中声明我们的属性,然后至于为什么要把.m 后缀改成.mm ,下面也会说,慢慢来。 下面就是我们为Message类建立ORM类字段绑定的过程:
1、定义该类遵守WCTTableCoding协议,可以在类声明上定义,也可以通过文件模版在category内定义(下面具体说)。这里推荐大家使用第二种,通过文件模板在category内定义,为什么要这样做,就是为了隔离Objective-C++代码,WCDB基于WINQ,引入了Objective-C++代码,所以对于引入了WCDB的源文件,都需要把后缀.m改为.mm,(这就是我们上面改后缀的原因)为减少影响范围,可以通过Objective-C的category特性将其隔离,达到只在model层使用Objective-C++编译,而不影响Controller和View。这一点在Wiki中是有提到的,
这样做的好处是不知道大家都有没有理解,这么说,要是你通过第一种方法,不通过category定义,而是选择了在类声明中写,这样的话Message.h 中就需要有宏WCDB_PROPERTY,这样你就在Message.h使用了WCDB的代码,当你把Message.h在其他Controller/View中引用的时候,那相应的Controller/View的.m就需要修改成.mm 。造成不必要的工作,但你用第二种方法写的时候,你就发现在Message.h中是没有任何的关于WCDB的代码的,后面你引用也不需要再去修改!希望大家理解这里。
在你项目中你集成了WCDB之后,你编译一下你的项目,你就可以看到上面我们说的模板文件,如下所示:
2、使用WCDB_PROPERTY宏在头文件声明需要绑定到数据库表的字段(也就是把你的表里面需要的字段在这里用这宏声明一次)
3、使用WCDB_IMPLEMENTATIO宏在类文件定义绑定到数据库表的类(把这个类绑定到数据库的表,你会在下面创建数据库的时候创建相应的表,表会和类绑定)
4、使用WCDB_SYNTHESIZE宏在类文件定义绑定到数据库表的类(第二步声明了表需要的字段,第三步绑定了表中的类,第四步就等于把表和字段绑定)
根据上面的步骤,就简单的完成了ORM的基本操作,想要了解更过的关于ORM宏的用法以及定义,还是查看Wiki文档:ORM使用教程
WCDB 初试 - CRUD
上面说完了ORM的操作,下面说说基本的数据库的创建以及CRUD的操作,在说下面之前,我们扯一点其他的,不知道会不会有人不知道该怎样去查看你建立的数据库内容,这里说推荐一个我自己一直在用的挺好用的工具 -- Navicat Premium ,你可以点击它去下载,提取密码是 e73y 。当然这不是我提供的,是简书同行提供的,谢谢无私奉献!
下载下来之后你点击安装的时候可能还需要密码:xclient.info
完了你打开,要是出现出损坏无法使用,打不开的情况,你需要设置:
注意: 要是你系统没有这个任何来源,终端命令: sudo spctl --master-disable
通过上面的操作,相信应该没什么问题了,接着简单一步带过怎样查看已有的数据库的:
左上角 Connection 选择 SQLite如下:
下面是正题,数据库的创建:
-(BOOL)creatDataBaseWithName:(NSString *)tableName{ //获取沙盒根目录 NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; // 文件路径 NSString *filePath = [documentsPath stringByAppendingPathComponent:@"model.sqlite"]; NSLog(@"path = %@",filePath); database = [[WCTDatabase alloc]initWithPath:filePath]; // 数据库加密 //NSData *password = [@"MyPassword" dataUsingEncoding:NSASCIIStringEncoding]; //[database setCipherKey:password]; //测试数据库是否能够打开 if ([database canOpen]) { // WCDB大量使用延迟初始化(Lazy initialization)的方式管理对象,因此SQLite连接会在第一次被访问时被打开。开发者不需要手动打开数据库。 // 先判断表是不是已经存在 if ([database isOpened]) { if ([database isTableExists:tableName]) { NSLog(@"表已经存在"); return NO; }else // 创建方法 return [database createTableAndIndexesOfName:tableName withClass:Message.class]; } } return NO; }
下面是基本的增删查改的操作,这里写的一些都是最基本最基本的,下面再说两个基本的事务处理方法,然后再把CRUD操作的代码放出来,我们说的也知识基本的,要是想灵活应用还是得慢慢学,掌握它。
1、 block处理事务,顾名思义就是把我们的事务处理放在block当中,如下我们举的这个例子:
// 另一种事务处理方法Block -(BOOL)insertMessageWithBlock{ BOOL commit = [database runTransaction:^BOOL{ BOOL ret = [self insertMessage]; if (ret) { return YES; }else return NO; } event:^(WCTTransactionEvent event) { NSLog(@"Event %d", event); }]; return commit; }
2、利用WCTTransaction来处理事务:
// WCTDatabase 事务操作,利用WCTTransaction -(BOOL)insertMessageWithTransaction{ BOOL ret = [database beginTransaction]; ret = [self insertMessage]; if (ret) { [database commitTransaction]; }else [database rollbackTransaction]; return ret; }
下面是我们写的简单的一个CRUD的操作的代码:
-(BOOL)insertMessage{ //插入 Message *message = [[Message alloc] init]; message.localID = 1; message.content = @"Hello, WCDB!"; message.createTime = [NSDate date]; message.modifiedTime = [NSDate date]; /* INSERT INTO message(localID, content, createTime, modifiedTime) VALUES(1, "Hello, WCDB!", 1496396165, 1496396165); */ return [database insertObject:message into:@"message"]; } -(BOOL)deleteMessage{ //删除 //DELETE FROM message WHERE localID>0; return [database deleteObjectsFromTable:@"message" where:Message.localID > 0]; } -(BOOL)updataMessage{ //修改 //UPDATE message SET content="Hello, Wechat!"; Message *message = [[Message alloc] init]; message.content = @"Hello, Wechat!"; //下面这句在17号的时候和微信团队的人在学习群里面沟通过,这个方法确实是不存在的,使用教程应该会更新,要是没更新注意这个方法 //BOOL result = [database updateTable:@"message" onProperties:Message.content withObject:message]; return [database updateAllRowsInTable:@"message" onProperty:Message.content withObject:message]; } //查询 -(NSArray *)seleteMessage{ //SELECT * FROM message ORDER BY localID NSArray<Message *> * message = [database getObjectsOfClass:Message.class fromTable:@"message" orderBy:Message.localID.order()]; return message; }
上面事务方面的暂时先说这么多,当然这方面的内容可以看具体的文档: 基础类、CRUD与Transaction
WCDB 其他
WCDB提供了对错误和性能的全局监控,可用于调试错误和性能。 也可以获取某个特定操作的错误信息。所有错误都以WCTError的形式出现。WCTError 就是继承自我们常见的NSError。
2、损坏修复
WCDB内建了修复工具,以应对数据库损坏,无法使用的情况。我们需要在数据库未损坏时,对数据库元信息定时进行备份,如下:
NSData *backupPassword = [@"MyBackupPassword" dataUsingEncoding:NSASCIIStringEncoding]; [database backupWithCipher:backupPassword];
注意:当检测到数据库损坏,即WCTError的type为WCTErrorTypeSQLite
,code为11或26(SQLITE_CORRUPT或SQLITE_NOTADB)时,可以进行修复,下面是官方给出的代码示例:
//Since recovering is a long time operation, you'd better call it in sub-thread. [view startLoading]; dispatch_async(DISPATCH_QUEUE_PRIORITY_BACKGROUND, ^{ WCTDatabase *recover = [[WCTDatabase alloc] initWithPath:recoverPath]; NSData *password = [@"MyPassword" dataUsingEncoding:NSASCIIStringEncoding]; NSData *backupPassword = [@"MyBackupPassword" dataUsingEncoding:NSASCIIStringEncoding]; int pageSize = 4096;//Default to 4096 on iOS and 1024 on macOS. [database close:^{ [recover recoverFromPath:path withPageSize:pageSize backupCipher:cipher databaseCipher:password]; }]; [view stopLoading]; });
3、性能数据
为了测试WCDB的性能数据,WCDB提供了benchmark,用于横向比较FMDB、纵向比较不同的参数配置,并可用于验证后续更多性能优化的效果。这里吧官方的文档给大家,有需要有兴趣的可以看看,这部分的内容以及下面从FMDB迁移到WCDB的内容我们会抓们整理出来,因为项目我也准备迁移到WCDB,等搞定会把相应新的分享出来。
这部分的内容上面提到自己在准备迁移到WCDB,所以就等迁移完成了会分享出心得。
上面的内容其实都是一些WCDB最基本的使用,也是希望WCDB大家都能掌握,既然是比FMDB好的存在我们也是肯定需要掌握的!