投稿文章,作者:@沒故事的卓同學(微博)
介紹
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
@interface Dog : RLMObject @property NSString *name; @property NSInteger age; @end Dog *mydog = [[Dog alloc] init];
存儲起來也非常簡單,獲取數據庫實例,在一個事務中進行寫入。
RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:mydog]; }];
方便的查詢,可以在一個查詢結果中再進行查詢。查詢的條件有著豐富的支持。
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,要寫這樣的代碼心裡也依舊難受:
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。
下面給出一個查詢的比較:
// 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:&error)
Realm則簡單的多:
// 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換掉。
再看下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有著優越的查詢性能。