realm 之於 iOS

MrPeak發表於2016-11-11

Realm是除了CoreData和Sqlite之外的第三個選擇,一個近幾年興起的全新的資料庫方案,一直保持著活躍的更新,而且引起了iOS開發圈廣泛的關注。Realm到底好不好用,又有哪些閃光點呢?下面通過一個實際的demo綜合比較CoreData和Realm在使用體驗上的差別。

Realm

Realm For iOS正式誕生於2014年左右,一開始就引起了不小的關注,兩年多的快速迭代使其日漸成熟。從下圖可以看出其活躍度:

11realm00

不瞭解Realm的同學可以先簡單看下其開發文件和我之前關於移動端資料庫的一篇介紹。看完之後不難發現,其實realm的真正對手是CoreData,二者都志在替開發者解決db端儲存之外,更提供model層的搭建。CoreData和Realm的官方demo程式碼都有在Controller當中直接存取資料的例子。

比如CoreData這篇NSFetchedResultsController的官方教程,教大家如何將CoreData提供的model和view做繫結。

比如Realm這篇使用Realm搭建Search Controller的教程,也是教如何在Controller當中直接使用Realm的model。

所以Realm一開始的設計就像是在瞄準CoreData,二者都不是簡單的儲存解決方案,都希望方便開發者使用,能以物件化的思維去使用資料庫。

Sqlite,CoreData,Realm三者之間的關係可以做個簡單的比喻,Sqlite提供的是漁網,CoreData和Realm則是直接已經將魚擺上餐桌,一旦拿起刀叉就沒有後悔的餘地,至於合不合胃口只能開發者自己去品嚐了。

所以下文將主要對比CoreData和Realm二者之間的差異,至於Realm與Sqlite的差異可對照CoreData和Sqlite。

Realm VS CoreData

接下來我們會建立一個demo project,從資料庫當中獲取user列表展示,同時還提供插入和刪除的操作,我們將從專案建立到業務使用,各方面來對照下二者的實際差異。

初始化建立database-CoreData

我們建立一個名為DBProfileDemo專案,如果勾選了使用CoreData,會在建立之後生成一個DBProfileDemo.xcdatamodeld檔案,選中該檔案可以通過以下圖形化的編輯器建立表及一些約束。

12realm01

為了建立user列表,我們先通過圖形化介面操作,建立UserEntity表,及相關的attribute。

13realm03

編輯完之後實際上啟動demo實際上就完成了初始化的工作,CoreData所需要的幾個關鍵元素都替你準備好了,比如我們建立一個新Entity所需要以下物件:

NSPersistentContainer, NSManagedObjectContext可以通過AppDelegate獲取,不需要開發者做額外的配置,Entity可以通過runtime以String為引數獲取到,到這步都還比較簡單,但我們一般需要建立自己的model類,方便後期新增一些業務方法,CoreData也提供了一套機制生成model類。

Xcode 8之前我們是通過選中DBProfileDemo.xcdatamodeld檔案,然後通過選單Editor->Create NSManagedObject Subclass來生成檔案。

Xcode 8新提供了新的codegen機制,自動生成相關的model類,生成方式有以下幾種:

realm 之於 iOS

這裡的操作就有些“巧妙”了,我們要先選擇Class Definitio,build專案,Xcode會在Derived Data一個很深的目錄裡生成我們的目標檔案:UserEntity+CoreDataClass.swift, UserEntity+CoreDataProperties.swift,將這兩個檔案拷貝出來新增至專案裡,再將Codegen選成Manual/None,否則會出現檔案重複的編譯錯誤。以後如果新增了新的attribute,需要再選Category/Extension,重複上述檔案的手動操作,最後再改回Manual/None。

以上的程式碼生成雖然都是auto的,但是一點都不cool!Xcode和Finder來回切換,codegen模式跟著改,Not Cool!model類的建立和維護這一步無法給五星好評。再看看realm的表現。

初始化建立database-Realm

Realm由於沒有整合到Xcode當中,需要下載對應的framework,或者通過pod安裝。匯入framework之後還可以安裝一個Xcode外掛,用於建立屬於realm的model,其實這個外掛非必須,完全可以通過建立普通類檔案的方式來建立新的realm model。匯入realm相關檔案之後,就可以直接開始使用realm了。

我們新建一個UserModel類:

並不需要像Xcode一樣在編輯器當中去顯式的建立一個表,可以認為model檔案生成之後其對應的表也就隨之存在了。如果我們想插入一個記錄到db中,可以直接執行如下程式碼:

物件的操作只需要一個realm物件即可,對應於CoreData當中的NSManagedObjectContext。而且開發者可以直接在UserModel當中新增domain logic,並不需要像CoreData一樣生成兩個檔案XXX+CoreDataClass.swift, XXX+CoreDataProperties.swift,每次新增新的property直接修改model類就可以了。而且可以看出和CoreData相比realm的程式碼非常簡潔。

所以在初始化建立環境Realm可以說是完勝CoreData。

寫操作及效能對比-CoreData

先來看下CoreData完成一次記錄建立所需的程式碼量:

CoreData做記錄插入的時候一定需要一個NSEntityDescription物件,而不能直接建立一個UserEntity物件,我猜測是因為需要通過NSEntityDescription來得知entity本身和其他entity的relation,Description類似於獲取table schema的概念,這點使用起來不太直觀。

再看下CoreData做100,000次插入所需時間,取10個樣本的平均值。

結果是:write cost for CoreData: 0.01137706024

寫操作及效能對比-Realm

Realm插入一條記錄所需程式碼:

可以看出對比CoreData程式碼簡潔不少,而且是直接對Model進行操作,不需要引入額外的物件。

再對比寫操作效能,每次10000條記錄,10個樣本的平均值

結果是:write cost for Realm: 0.00741579052

上述結果是沒有建立index的情況,如果針對userID建立index,測試的結果為:

write cost for CoreData: 0.01643883225 write cost for Realm: 0.01866273956

可以看出二者在寫操作效能上大致接近,不建index,realm略微勝出,建index,CoreData表現稍好,但這點效能的差異對實際專案的影響幾乎可以忽略,Realm的優勢依舊是在程式碼的表達上,realm更為清晰。

讀操作及效能對比-CoreData

批量讀取記錄程式碼:

讀操作通過一個物件NSFetchRequest來完成,同時需要傳入entity的名字。

效能方面,我們讀取上面寫入的100,000條記錄,讀10次取平均值。

輸出為:read cost for CoreData: 0.00231904814

讀操作及效能對比-Realm

批量讀取記錄:

程式碼非常乾淨,不需要引入額外的物件來描述查詢請求。

效能對比,100,000條記錄,10次取平均值:

輸出為:read cost for Realm: 0.00098051783

以上結果是不建立Index的case,如果針對userID建立Index,100,000次查詢,測試結果為:

read cost for CoreData: 0.0783214620999998 read cost for Realm: 0.098253140500001

如果沒有Index,Realm測試中批量read的效能大致是CoreData的2.3倍,針對userID建立Index情況下CoreData效能略微勝出,但在API友好度上依舊是Realm勝出。

實際上關於二者讀寫的效能對比,Realm官方在2014年做過一份資料對比,宣稱其無論insert還是query效能都是CoreData的10倍左右。我這次都是使用Realm和CoreData的最新版本,實際測試結果二者表現是接近的,或者這兩年Realm和CoreData都做過一些底層更新。

多執行緒設計

一言以蔽之,在多執行緒設計上,Realm面臨和CoreData同樣的困境,這種困境究其根本是源自於二者對model layer的相同處理。

如果想要把CoreData的寫操作都非同步到子執行緒當中,那麼需要在子執行緒當中使用自己的NSManagedObjectContext,需要遵循以下兩個規則:

  • 不同的執行緒要建立自己的NSManagedObjectContext,維護各自的object graph。
  • NSManagedObject不能跨執行緒傳遞使用,只要通過傳遞NSManagedObjectID,再通過ID去從各自的Context中獲取Object。

在Realm的世界裡,和Context對應的概念是Realm物件,同樣realm物件也不能跨執行緒使用,也不能在不同的執行緒之間共享一份model。Realm的官方表述如下:

Sharing Realm instances across threads is not supported. Realm instances accessing the same Realm file must also all use the same Realm.Configuration.

也就意味著,如果你在app的應用層直接使用CoreData或者Realm的model例項,在操作對應model的時候,你需要十分清楚model是在哪個執行緒建立的,哪裡建立才能哪裡使用。這顯然很不符合物件化的思維,給model的使用帶來了額外的負擔。

當然Realm作為新生事物,還是做了一些改良。比如在CoreData當中多個執行緒的context如果需要同步object graph,需要顯式的merge context或者建立parent-children context的層級結構。在realm世界,如果realm物件所處的執行緒擁有runloop,每個runloop開始的時候會自動去同步一次,沒有runloop的工作執行緒需要顯式的呼叫Realm.refresh()(呼叫寫操作的時候也會自動同步一次)。這種做法的確比CoreData在使用上負擔更小。

Realm特色

Realm將所有和開發者的互動都放在model類裡面,不需要像CoreData一樣在圖形編輯器和類檔案二者之間切換。

一對一的關係:

一對多的關係:

建立索引:

建立Primary Key:

以上這些都定義在model類檔案當中,一目瞭然。

Realm的集合操作更靈活,支援Chaining。

Realm還支援Notification模式,這對於CoreData來說是個全新的設計,這個通知不僅僅是KVO層面,Realm還可以在更大的粒度上對資料的變化做出監聽,比如監聽單個object,或者object的集合變化。我們可以通過如下程式碼監聽物件的變化:

不要小看這個Notification機制,它的核心思想和當下流行的Reactive Programming同出一宗,如果大面積使用,可以搭建一個基於使用者行為驅動的App架構。

我之前寫過部落格介紹如何搭建資料驅動型的iOS App架構,所以看到Realm的Notification機制之後倍感親切。總體來說,資料的變化按照粒度大小可以依次分為:property變化,object變化,object集合變化。粒度越小,變化的頻率越高,意味著會觸發更多的UI執行緒重新整理操作,如果不仔細的設計可能會導致意外的效能問題。

Realm相較於CoreData還有個優勢,Android端和iOS端可以採用一致的儲存設計,甚至可以通過工具或者指令碼生成兩個端相同的model,model層的設計一致會更多的驅使兩個端在業務設計上也更接近,在遇到問題和尋求解決方案時避免踩兩次坑。

總結:

Realm和CoreData相比較,二者的最新版本在效能上似乎並沒有太大的差異,不過Realm的API設計和總體使用體驗比CoreData要好很多,物件化的設計上更簡潔,學習曲線沒有CoreData陡。對App架構的影響上,二者的設計思路非常接近,都是通過物件化的介面來完成資料的儲存和model layer的搭建,CoreData在架構設計遇到的問題Realm並沒有太大的改善。

我個人對於CoreData或者Realm的使用建議是:不要在應用層(Controller中)大量引入CoreData和Realm相關的程式碼,將其通過另一層封裝隔離在單獨的model layer,所有應用層使用到的model都通過model layer做一次轉換,避免Controller直接對第三方的model產生依賴,這樣即使遇到問題,在做資料遷移的時候會少很多工作量。

至於在Sqlite,CoreData,Realm三者之間如何選擇?

個人建議:

對於已經使用CoreData或者Sqlite方案的App,沒必要遷移至Realm。

對於新專案,如果規模小,資料儲存和查詢複雜度小,可以使用Realm提升model layer的搭建速度和開發體驗。

如果新專案業務複雜度高,資料讀寫頻繁,直接採用Realm還是存在一定的風險,團隊如果在sqlite上已經有一定的技術積累,還是應該採用更成熟的sqlite方案,自己完成model layer的搭建。如果是在CoreData和Realm之間做選擇,我推薦Realm,更容易上手,程式碼也更簡潔,Realm團隊對於問題處理的態度也非常積極迅速,總體來說,Realm是比CoreData更優秀的方案,對開發者更友好。

上述測試專案程式碼就不放github了,感興趣的同學可以在我公眾號MrPeakTech回覆realm獲取下載連結。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

realm 之於 iOS

相關文章