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好的存在我們也是肯定需要掌握的!