介紹
realm是一個跨平臺移動資料庫引擎,支援iOS、OS X(Objective‑C和Swift)以及Android。
2014年7月釋出。由YCombinator孵化的創業團隊歷時幾年打造,是第一個專門針對移動平臺設計的資料庫。目標是取代SQLite。
為了徹底解決效能問題,核心資料引擎用C++打造,並不是建立在SQLite之上的ORM。如果對資料引擎實現想深入瞭解可以檢視:Realm 核心資料庫引擎探祕。因此得到的收益就是比普通的ORM要快很多,甚至比單獨無封裝的SQLite還要快。
因為是ORM,本身在設計時也針對移動裝置(iOS、Android),所以非常簡單易用,學習成本很低。
碾壓級效能
資料引自:introducing-realm
每秒能在20萬條資料中進行查詢後count的次數。realm每秒可以進行30.9次查詢後count。
在20萬條中進行一次遍歷查詢,資料和前面的count相似:realm一秒可以遍歷20萬條資料31次,而coredata只能進行兩次查詢。
這是在一次事務每秒插入資料的對比,realm每秒可以插入9.4萬條記錄,在這個比較裡純SQLite的效能最好,每秒可以插入17.8萬條記錄。然而封裝了SQLite的FMDB的成績大概是realm的一半。
簡單易用
例項程式碼語言是Objective‑C。
Realm物件和其他物件沒有太大區別,只是需要繼承RLMObject
1 2 3 4 5 6 |
@interface Dog : RLMObject @property NSString *name; @property NSInteger age; @end Dog *mydog = [[Dog alloc] init]; |
儲存起來也非常簡單,獲取資料庫例項,在一個事務中進行寫入。
1 2 3 4 5 |
RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:mydog]; }]; |
方便的查詢,可以在一個查詢結果中再進行查詢。查詢的條件有著豐富的支援。
1 2 3 4 |
RLMResults *r = [Dog objectsWhere:@"age > 8"]; // Queries are chainable r = [r objectsWhere:@"name contains 'Rex' AND name BEGINSWITH '大'"]; |
zero-copy和懶載入
在通常的資料庫中,資料大多數時間都靜靜地呆在硬碟當中。當你訪問 NSManagedObject 物件中的某個屬性的時候,Core Data 會將這個請求轉換為一組 SQL 語句,如果還未連線資料庫的話則建立一個資料庫連線,然後將這個 SQL 語句傳送給硬碟,執行檢索,從匹配檢索的結果中讀取所有的資料,然後將它們放到記憶體當中(也就是記憶體分配)。然而,這時候你需要對其格式進行反序列化(deserialize),因為硬碟上儲存的格式不能直接在記憶體中使用,這意味著你需要調整位,以便 CPU 能夠對其進行處理。
然而Realm跳過了整個拷貝資料到記憶體的過程,稱之為zero-copy。做到這點是因為檔案始終是記憶體對映的,無論檔案是或否在記憶體當中,你都能夠訪問檔案的任何內容。關於核心檔案格式的重要一點就是,確保硬碟上的檔案格式都是記憶體可讀的,這樣就無需執行任何反序列化操作了。
這樣就帶來了一個問題,難道資料全載入到記憶體裡了?所以這裡懶載入應運而生,比如在查詢到一組資料後,只有當你真正訪問物件的時候才真正載入進來。
VS SQLite
SQLite第一個版本釋出於2000年,至今已16年。以當今的角度來看,它的程式設計抽象程度非常低。業務上我們其實只想把這些物件存進去,可以查詢出來。
即便已經是封裝過的FMDB,要寫這樣的程式碼心裡也依舊難受:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"]; if (![db open]) { [db release]; return; } NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);" "create table bulktest2 (id integer primary key autoincrement, y text);" "create table bulktest3 (id integer primary key autoincrement, z text);" "insert into bulktest1 (x) values ('XXX');" "insert into bulktest2 (y) values ('YYY');" "insert into bulktest3 (z) values ('ZZZ');"; success = [db executeStatements:sql]; sql = @"select count(*) as count from bulktest1;" "select count(*) as count from bulktest2;" "select count(*) as count from bulktest3;"; success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) { NSInteger count = [dictionary[@"count"] integerValue]; XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary); return 0; }]; [db close]; |
VS CoreData
詳細的比較推薦看這篇:CoreData VS Realm。
下面給出一個查詢的比較:
1 2 3 4 5 6 7 8 |
// Core Data let fetchRequest = NSFetchRequest(entityName: "Specimen") let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString) fetchRequest.predicate = predicate let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) fetchRequest.sortDescriptors = [sortDescriptor] let error = NSError() let results = managedObjectContext?.executeFetchRequest(fetchRequest, error: |
Realm則簡單的多:
1 2 3 |
// Realm let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString); let specimens = Specimen.objectsWithPredicate(predicate).arraySortedByProperty("name", ascending: true) |
總結一下Realm對CoreData的優勢:
不需要架構Context那種煩人的東西
CoreData 是一個博大精深的技術,不要妄想幾天之內可以用的很溜。
不服看看這些書。
支援 NSPredicate
從 CoreData 轉過來並沒有太多的不適應。
CoreData多個持久化檔案很麻煩,Realm輕鬆支援這個功能
劣勢:
是會增加應用大概1MB的體積。CoreData原生支援,不會增加App體積。
雖然看上去很厲害,但是這麼新靠譜嗎
Realm大部分原始碼公開在github上:realm。專案在新建不到兩年裡,已經得到開源社群大量關注:
官方也承諾會持續解決使用者反饋的各種問題。也可以直接在他們twitter上去@他們。
就算靠譜,有別人在用嗎
推薦閱讀這篇部落格,作者介紹了他痛下決心拋棄CoreData後,如何安全遷移至Realm:《高速公路換輪胎——為遺留系統替換資料庫》。
在多年以前,人們做了個決策,用CoreData做本地儲存,替換掉NSUserDefaults。這之間的歷史已經遠不可考,但自從我加入專案以來,整個團隊已經被它高昂的學習曲線、複雜的資料Migration流程以及過時陳舊的設計折磨的苦不堪言。於是我們決心把CoreData換掉。
文/涼粉小刀(簡書作者)
原文連結:http://www.jianshu.com/p/d684693f1d77
再看下SO的情況:
已經有大概兩萬條相關結果,你不是一個人!
需要知道的一些問題
其實我自己覺得這些是可以接受的問題。技術很多時候就是權衡,為了達到一些目的,總是要犧牲掉一些東西。
- 所有的儲存物件需要繼承RealmObject
比如我現在的專案的資料從網路請求回來都會繼承自己寫的一個方便解析的基類,在這裡就需要做出一些適應。
但是該問題在swift中是不存在的。因為swift是天生的面向協議程式設計正規化。 - 不能自定義getter、setter
realm會自動生成getter、setter,如果自定義getter、setter儲存就會有影響。如果要規避這個問題,可以通過設定這個物件的忽略屬性。
比如有個屬性id,需要自定義setter。可以在物件屬性裡把id設定為忽略屬性,這樣realm就不會為它自動生成getter、setter,但是也不會把id存入資料庫。接著自定義一個用於儲存的屬性比如realm_id。在id的setter中可以把把值也賦給realm_id。
這個問題在swift中也是不存在的,因為swfit中使用的是willset、didset這種通知機制。 - 查詢的結果不是陣列
為了能夠支援查詢結果的鏈式查詢,realm自定義了一個集合型別。可以列舉,但是不是熟悉的陣列了。又因為realm的懶載入機制,所以不建議在資料層把這個列舉轉成陣列型別。這樣的缺點就是上層知道了資料的儲存邏輯。嚴格的說這裡不應該讓上層知道。但是這樣設計也許是為了方便上層進行再次檢索,realm有著優越的查詢效能。