結識 Realm 的催化劑
? ? ? ?在我們公司的項目迭代中,由于在之前的聊天這個模塊關于用戶信息的傳值有問題,而之前因為項目經過很多開發(fā)者的手,且不提整體的架構有多混亂,就單說緩存這塊,就是亂的不行,有的地方用CoreData,有的地方用FMDB, 而且封裝的Manager中方法的聲明很亂,存取的邏輯也不是很清晰,于是造成了很多我需要取到數(shù)據(jù)的時候,根本取不到,而當我修改大部分本次版本迭代的需求時,發(fā)現(xiàn)這個取不到值問題如果繼續(xù)沿用之前的邏輯就會非常的麻煩,我要用很多額外方法去跳過之前的坑,只是我決定,在群組聊天的功能中,將用戶數(shù)據(jù)的傳值這塊邏輯重構. ? ??
? ? ? ? 傳值的邏輯還是比較好重構的,我將原有用在這里的 CoreData代碼全部都清掉,讓整體功能即使不依賴于緩存,依舊可以正確及時的取到需要的數(shù)據(jù),只是需要等待服務器的響應,但是如果每次都去走服務端的網絡請求,那么體驗就太差了,那么緩存便是很重要的一步.
? ? ? ? 之前說過,現(xiàn)有項目有,有的地方采用了 CoreData,有的用了 FMDB,十分混亂,而我們的用戶量還并涉及不到數(shù)據(jù)遷移的問題.所以我想采用另一套緩存框架來完成我的需求,那么我第一個想到的就是 Realm.
初識 Realm
? ? ? ? Realm是一個跨平臺的移動數(shù)據(jù)庫引擎,而且,它是專門為移動端數(shù)據(jù)應用設計的數(shù)據(jù)持久化方案.不論是 CoreData,還是傳統(tǒng)的SQLite,代碼都些許冗余.CoreData的笨拙的API和FMDB相對不那么面向對象的操作方式,可能會很多人望而卻步或萌生停用的念頭,那么這個時候,Realm 出現(xiàn)了.
? ? ? Realm既不是 CoreData,也不是SQLite,它擁有自己的數(shù)據(jù)庫存取引擎,它可以跨平臺使用,也意味著更加快速的存取速度,官方給出的Realm的存取速度比 CoreData快了3倍,但是據(jù)說在實際使用中,當數(shù)據(jù)量很大時候,Realm的速度比 CoreData 快了不值30倍.
? ? ? 說了這么多,你一定對 Realm 也有了些許好感,這篇文章中我并打算介紹關于 Realm 的使用方法,因為文檔Relam的官方文檔中寫的清清楚楚,網上很多大神也做了相關介紹,但是大多數(shù)的博客中方法的介紹都是局限在了 Demo 的使用中,真的作用于項目的很少,我在此也算賣弄賣弄我在植入到實際項目時所遇到的小坑或者經驗吧.
??1.由于 RLMArray 的關系,這句話一定要寫,來定義 RLMArray 中的實例,不然會崩潰
? ?2.由于數(shù)據(jù)模型已經由繼承與 NSObject 的 Model, 改為了繼承于 RLMObject,所以在使用 KVC 的時候一定要注意.
3.主鍵
如果你想要更新數(shù)據(jù),主鍵是不錯的選擇
4.線程
? ? ? ?線程問題的坑是我這篇文章所要說的重點.其實 Realm 在關于線程的處理上已經幫我們做了很多事情,我自己并不需要講過多的精力放在線程上,但是 Realm 本身的線程管理非常嚴格,所以我們必須遵守Realm 的使用方式,這就使得坑與有點并存
? ? ? ? 原本我最開始關于緩存的設計思路是,我從服務端拿到了數(shù)據(jù),那么我會把數(shù)據(jù)放入內存中的數(shù)組容器中,再存入數(shù)據(jù)庫中,那么下次進入這個頁面我就可以先從數(shù)據(jù)庫中拿到數(shù)據(jù)后放入容器,再通過服務端進行更新,也是說在當前 Controller 中,我只需要通過數(shù)組容器進行賦值就可以了.然而 Realm 并不允許你這樣,當我將數(shù)據(jù)存入 Realm 后,我還沒有進行取數(shù)據(jù)的操作,我只是用數(shù)據(jù)容器,但這時,程序崩潰了, WTF????!!!! 查看一下崩潰信息 Realm accessed from incorrect thread(從錯誤的線程訪問),當時我就委屈了,我存的時候沒有崩潰,也沒取啊,怎么就崩潰的,后來我又不得已的用蹩腳的英語水平仔細研讀了一下原文文檔.
蛋疼的開始
臥槽???看的我是萬臉懵逼!!
? ? ? ?于是我就交了個 Realm的技術交流群,開始,還有人跟我交流交流使用心德,等把把圖貼出來,石沉大海一般,可能是兩張截圖暴露我的智商和理解能力,大家不想跟差生玩兒...好吧,本著 API 文檔就像游戲攻略一樣的原則,看不懂的,就帶著疑問去玩一玩...那么既然增沒事兒,改刪查也都沒做,那問題會不會出現(xiàn)在了 RLMObject 的調用上,于是在遍歷使用之前說的數(shù)組容器的地方,打印了當前線程,嗯,果然不是主線程,那既然說同一個 Realm 只要提交了寫入就可以在其他線程改變,那我于是試了試 [RLMObject ?objectWhere:@"查詢條件"],發(fā)現(xiàn)就完全沒有問題的,那原因到底是什么呢?于是我又開始啃文檔
? ? ? ? 這就非常清晰了, Realm 本身在工程中的調用也是個單例類[RLMRealm defaultRealm],所以只要是同一個 Realm, 就可以在任意線程,哪怕是多個線程中,隨意使用,不需要鎖,只需要將 commitWriteTransaction就可以.但是 RLMObject 的使用的限制就非常嚴格的,主線程里創(chuàng)建的 RLMObject 就只能在主線程里用,在其他線程中調用的這個它的實例就會拋出異常,有人說,這是 Realm的線程坑,但是我覺得這個是 Realm 對線程做的最好的處理
為此我特意寫了一段非常欠打的代碼,來驗證 Realm 是如何處理并發(fā)問題的
#pragma mark 這是第一個子線程? 這里面進行更新寫入
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current ======= %@",[NSThread currentThread]);
for (int i = 0; i < 35; i ++) {
InformationUpdateModel *model = [[InformationUpdateModel alloc] init];
model.name = [NSString stringWithFormat:@"草鞋%d號",i];
model.age = i;
RLMRealm *relam = [RLMRealm defaultRealm];
[relam transactionWithBlock:^{
[relam addOrUpdateObject:model];
[relam commitWriteTransaction];
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
[InfomationModel allObjects];
});
});
#pragma mark 這是第二個子線程? 這里面是查詢
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current ======= %@",[NSThread currentThread]);
[InfomationModel allObjects];
});
#pragma mark 這里是第三個子線程? 這里是更新寫入加查詢? 回到主線程后繼續(xù)更新寫入加查詢
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current ======= %@",[NSThread currentThread]);
for (int i = 60; i < 80; i ++) {
InformationUpdateModel *model = [[InformationUpdateModel alloc] init];
model.name = [NSString stringWithFormat:@"草鞋%d號",i];
model.age = i;
RLMRealm *relam = [RLMRealm defaultRealm];
[relam transactionWithBlock:^{
[relam addOrUpdateObject:model];
[relam commitWriteTransaction];
}];
}
[InfomationModel allObjects];
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 40; i < 50; i ++) {
InformationUpdateModel *model = [[InformationUpdateModel alloc] init];
model.name = [NSString stringWithFormat:@"草鞋%d號",i];
model.age = i;
RLMRealm *relam = [RLMRealm defaultRealm];
[relam transactionWithBlock:^{
[relam addOrUpdateObject:model];
[relam commitWriteTransaction];
}];
}
});
});
我直接開了三個子線程,并在其中用同一個 Realm 各自存取
從時間上來看,形成了一個小規(guī)模的并發(fā),也是說,會在Realm可能同時即被讀,又被寫,但是完全沒有報出異常,程序流暢運行,數(shù)據(jù)也沒有出現(xiàn)錯誤.
關于 Realm 就先說到這里,其實 Realm 也有很多不便之處,比如我們的模型必須要繼承RLMObject,他的RLMArray也并不是 NSArray類型,所以從代碼的獨立性角度上來說,就不是特別的完美,如果本身項目中有一套完整嚴格的數(shù)據(jù)協(xié)議,那么 Realm 可能就不會是一個好的選擇,而且本身自帶 Model,也被很多架構師的去 Model 化思想想違背,但這并不妨礙它是一套專門用于移動端,速度效率超快, API 非常簡潔,線程處理非常棒的持久化解決方案.
后記
? ? ? ? 第一次寫技術文章,誠惶誠恐,其實從代碼角度來說,并沒有分享什么優(yōu)質代碼,而且廢話比較多,可能是因為我個人嘴太碎.這篇文章只是針對自己對于 Realm 的使用做了一些總結,并希望分享出去,這樣如果我有理解的不對或者值得討論的地方,也可以盡快的糾正,當然如果這邊文章可以幫到誰,哪怕只有那么一丟丟,我也就心滿意足了
? ? ? ? 其實關于 Realm 的線程處理還有很多更好的方法,如果有機會和時間我會隨著業(yè)務的深入,再次進行探索,并將心得分享出來共勉.
? ? ? ?如果有問題或者不對地方我會及時更正.
/*************************** ?2016 - 10 - 18 第一次更新 ****************************/