
介紹
realm是一個跨平臺移動數(shù)據(jù)庫引擎,支持iOS、OS X(Objective?C和Swift)以及Android。
2014年7月發(fā)布。由YCombinator孵化的創(chuàng)業(yè)團隊歷時幾年打造,是第一個專門針對移動平臺設計的數(shù)據(jù)庫。目標是取代SQLite。
為了徹底解決性能問題,核心數(shù)據(jù)引擎用C++打造,并不是建立在SQLite之上的ORM。如果對數(shù)據(jù)引擎實現(xiàn)想深入了解可以查看:Realm 核心數(shù)據(jù)庫引擎探秘。因此得到的收益就是比普通的ORM要快很多,甚至比單獨無封裝的SQLite還要快。
因為是ORM,本身在設計時也針對移動設備(iOS、Android),所以非常簡單易用,學習成本很低。
碾壓級性能
數(shù)據(jù)引自:introducing-realm
每秒能在20萬條數(shù)據(jù)中進行查詢后count的次數(shù)。realm每秒可以進行30.9次查詢后count。

在20萬條中進行一次遍歷查詢,數(shù)據(jù)和前面的count相似:realm一秒可以遍歷20萬條數(shù)據(jù)31次,而coredata只能進行兩次查詢。

這是在一次事務每秒插入數(shù)據(jù)的對比,realm每秒可以插入9.4萬條記錄,在這個比較里純SQLite的性能最好,每秒可以插入17.8萬條記錄。然而封裝了SQLite的FMDB的成績大概是realm的一半。

簡單易用
實例代碼語言是Objective?C。
Realm對象和其他對象沒有太大區(qū)別,只是需要繼承RLMObject
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end
Dog *mydog = [[Dog alloc] init];
存儲起來也非常簡單,獲取數(shù)據(jù)庫實例,在一個事務中進行寫入。
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:mydog];
}];
方便的查詢,可以在一個查詢結(jié)果中再進行查詢。查詢的條件有著豐富的支持。
RLMResults *r = [Dog objectsWhere:@"age > 8"];
// Queries are chainable
r = [r objectsWhere:@"name contains 'Rex' AND name BEGINSWITH '大'"];
zero-copy和懶加載
在通常的數(shù)據(jù)庫中,數(shù)據(jù)大多數(shù)時間都靜靜地呆在硬盤當中。當你訪問 NSManagedObject 對象中的某個屬性的時候,Core Data 會將這個請求轉(zhuǎn)換為一組 SQL 語句,如果還未連接數(shù)據(jù)庫的話則創(chuàng)建一個數(shù)據(jù)庫連接,然后將這個 SQL 語句發(fā)送給硬盤,執(zhí)行檢索,從匹配檢索的結(jié)果中讀取所有的數(shù)據(jù),然后將它們放到內(nèi)存當中(也就是內(nèi)存分配)。然而,這時候你需要對其格式進行反序列化(deserialize),因為硬盤上存儲的格式不能直接在內(nèi)存中使用,這意味著你需要調(diào)整位,以便 CPU 能夠?qū)ζ溥M行處理。
然而Realm跳過了整個拷貝數(shù)據(jù)到內(nèi)存的過程,稱之為zero-copy。做到這點是因為文件始終是內(nèi)存映射的,無論文件是或否在內(nèi)存當中,你都能夠訪問文件的任何內(nèi)容。關于核心文件格式的重要一點就是,確保硬盤上的文件格式都是內(nèi)存可讀的,這樣就無需執(zhí)行任何反序列化操作了。
這樣就帶來了一個問題,難道數(shù)據(jù)全加載到內(nèi)存里了?所以這里懶加載應運而生,比如在查詢到一組數(shù)據(jù)后,只有當你真正訪問對象的時候才真正加載進來。
VS SQLite
SQLite第一個版本發(fā)布于2000年,至今已16年。以當今的角度來看,它的編程抽象程度非常低。業(yè)務上我們其實只想把這些對象存進去,可以查詢出來。
即便已經(jīng)是封裝過的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)
總結(jié)一下Realm對CoreData的優(yōu)勢:
不需要架構Context那種煩人的東西
CoreData 是一個博大精深的技術,不要妄想幾天之內(nèi)可以用的很溜。
不服看看這些書。



支持 NSPredicate
從 CoreData 轉(zhuǎn)過來并沒有太多的不適應。
CoreData多個持久化文件很麻煩,Realm輕松支持這個功能
劣勢:
是會增加應用大概1MB的體積。CoreData原生支持,不會增加App體積。
雖然看上去很厲害,但是這么新靠譜嗎
Realm大部分源碼公開在github上:realm。項目在新建不到兩年里,已經(jīng)得到開源社區(qū)大量關注:

官方也承諾會持續(xù)解決用戶反饋的各種問題。也可以直接在他們twitter上去@他們。
就算靠譜,有別人在用嗎
推薦閱讀這篇博客,作者介紹了他痛下決心拋棄CoreData后,如何安全遷移至Realm:《高速公路換輪胎——為遺留系統(tǒng)替換數(shù)據(jù)庫》。
在多年以前,人們做了個決策,用CoreData做本地存儲,替換掉NSUserDefaults。這之間的歷史已經(jīng)遠不可考,但自從我加入項目以來,整個團隊已經(jīng)被它高昂的學習曲線、復雜的數(shù)據(jù)Migration流程以及過時陳舊的設計折磨的苦不堪言。于是我們決心把CoreData換掉。
文/涼粉小刀(簡書作者)
原文鏈接:http://www.itdecent.cn/p/d684693f1d77
再看下SO的情況:

已經(jīng)有大概兩萬條相關結(jié)果,你不是一個人!
需要知道的一些問題
其實我自己覺得這些是可以接受的問題。技術很多時候就是權衡,為了達到一些目的,總是要犧牲掉一些東西。
- 所有的存儲對象需要繼承RealmObject
比如我現(xiàn)在的項目的數(shù)據(jù)從網(wǎng)絡請求回來都會繼承自己寫的一個方便解析的基類,在這里就需要做出一些適應。
但是該問題在swift中是不存在的。因為swift是天生的面向協(xié)議編程范式。 - 不能自定義getter、setter
realm會自動生成getter、setter,如果自定義getter、setter存儲就會有影響。如果要規(guī)避這個問題,可以通過設置這個對象的忽略屬性。
比如有個屬性id,需要自定義setter??梢栽趯ο髮傩岳锇裪d設置為忽略屬性,這樣realm就不會為它自動生成getter、setter,但是也不會把id存入數(shù)據(jù)庫。接著自定義一個用于存儲的屬性比如realm_id。在id的setter中可以把把值也賦給realm_id。
這個問題在swift中也是不存在的,因為swfit中使用的是willset、didset這種通知機制。 - 查詢的結(jié)果不是數(shù)組
為了能夠支持查詢結(jié)果的鏈式查詢,realm自定義了一個集合類型。可以枚舉,但是不是熟悉的數(shù)組了。又因為realm的懶加載機制,所以不建議在數(shù)據(jù)層把這個枚舉轉(zhuǎn)成數(shù)組類型。這樣的缺點就是上層知道了數(shù)據(jù)的存儲邏輯。嚴格的說這里不應該讓上層知道。但是這樣設計也許是為了方便上層進行再次檢索,realm有著優(yōu)越的查詢性能。
歡迎關注我的微博:@沒故事的卓同學