介紹
realm是一個(gè)跨平臺(tái)移動(dòng)數(shù)據(jù)庫(kù)引擎埠胖,支持iOS肛炮、OS X(Objective-C和Swift)以及Android举户。
2014年7月發(fā)布蕉世。由YCombinator孵化的創(chuàng)業(yè)團(tuán)隊(duì)歷時(shí)幾年打造,是第一個(gè)專門針對(duì)移動(dòng)平臺(tái)設(shè)計(jì)的數(shù)據(jù)庫(kù)狭姨。目標(biāo)是取代SQLite宰啦。
為了徹底解決性能問(wèn)題,核心數(shù)據(jù)引擎C++打造饼拍,并不是建立在SQLite之上的ORM赡模。如果對(duì)數(shù)據(jù)引擎實(shí)現(xiàn)想深入了解可以查看:Realm 核心數(shù)據(jù)庫(kù)引擎探秘。因此得到的收益就是比普通的ORM要快很多师抄,甚至比單獨(dú)無(wú)封裝的SQLite還要快漓柑。
因?yàn)槭荗RM,本身在設(shè)計(jì)時(shí)也針對(duì)移動(dòng)設(shè)備(iOS、Android)辆布,所以非常簡(jiǎn)單易用瞬矩,學(xué)習(xí)成本很低。
碾壓級(jí)性能
數(shù)據(jù)引自:introducing-realm
每秒能在20萬(wàn)條數(shù)據(jù)中進(jìn)行查詢后count的次數(shù)锋玲。realm每秒可以進(jìn)行30.9次查詢后count景用。
在20萬(wàn)條中進(jìn)行一次遍歷查詢,數(shù)據(jù)和前面的count相似:realm一秒可以遍歷20萬(wàn)條數(shù)據(jù)31次惭蹂,而coredata只能進(jìn)行兩次查詢伞插。
這是在一次事務(wù)每秒插入數(shù)據(jù)的對(duì)比,realm每秒可以插入9.4萬(wàn)條記錄盾碗,在這個(gè)比較里純SQLite的性能最好媚污,每秒可以插入17.8萬(wàn)條記錄。然而封裝了SQLite的FMDB的成績(jī)大概是realm的一半廷雅。
簡(jiǎn)單易用
實(shí)例代碼語(yǔ)言是Objective-C耗美。
Realm對(duì)象和其他對(duì)象沒(méi)有太大區(qū)別,只是需要繼承RLMObject
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end
Dog *mydog = [[Dog alloc] init];
存儲(chǔ)起來(lái)也非常簡(jiǎn)單榜轿,獲取數(shù)據(jù)庫(kù)實(shí)例幽歼,在一個(gè)事務(wù)中進(jìn)行寫入。
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:mydog];
}];
方便的查詢谬盐,可以在一個(gè)查詢結(jié)果中再進(jìn)行查詢甸私。查詢的條件有著豐富的支持。
RLMResults *r = [Dog objectsWhere:@"age > 8"];
// Queries are chainable
r = [r objectsWhere:@"name contains 'Rex' AND name BEGINSWITH '大'"];
zero-copy和懶加載
在通常的數(shù)據(jù)庫(kù)中飞傀,數(shù)據(jù)大多數(shù)時(shí)間都靜靜地呆在硬盤當(dāng)中皇型。當(dāng)你訪問(wèn) NSManagedObject 對(duì)象中的某個(gè)屬性的時(shí)候,Core Data 會(huì)將這個(gè)請(qǐng)求轉(zhuǎn)換為一組 SQL 語(yǔ)句砸烦,如果還未連接數(shù)據(jù)庫(kù)的話則創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接弃鸦,然后將這個(gè) SQL 語(yǔ)句發(fā)送給硬盤,執(zhí)行檢索幢痘,從匹配檢索的結(jié)果中讀取所有的數(shù)據(jù)唬格,然后將它們放到內(nèi)存當(dāng)中(也就是內(nèi)存分配)。然而颜说,這時(shí)候你需要對(duì)其格式進(jìn)行反序列化(deserialize)购岗,因?yàn)橛脖P上存儲(chǔ)的格式不能直接在內(nèi)存中使用,這意味著你需要調(diào)整位门粪,以便 CPU 能夠?qū)ζ溥M(jìn)行處理喊积。
然而Realm跳過(guò)了整個(gè)拷貝數(shù)據(jù)到內(nèi)存的過(guò)程,稱之為zero-copy玄妈。做到這點(diǎn)是因?yàn)槲募冀K是內(nèi)存映射的乾吻,無(wú)論文件是或否在內(nèi)存當(dāng)中髓梅,你都能夠訪問(wèn)文件的任何內(nèi)容。關(guān)于核心文件格式的重要一點(diǎn)就是绎签,確保硬盤上的文件格式都是內(nèi)存可讀的枯饿,這樣就無(wú)需執(zhí)行任何反序列化操作了。
這樣就帶來(lái)了一個(gè)問(wèn)題诡必,難道數(shù)據(jù)全加載到內(nèi)存里了鸭你?所以這里懶加載應(yīng)運(yùn)而生,比如在查詢到一組數(shù)據(jù)后擒权,只有當(dāng)你真正訪問(wèn)對(duì)象的時(shí)候才真正加載進(jìn)來(lái)。
VS SQLite
SQLite第一個(gè)版本發(fā)布于2000年阁谆,至今已16年碳抄。以當(dāng)今的角度來(lái)看,它的編程抽象程度非常低场绿。業(yè)務(wù)上我們其實(shí)只想把這些對(duì)象存進(jìn)去剖效,可以查詢出來(lái)。
即便已經(jīng)是封裝過(guò)的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
詳細(xì)的比較推薦看這篇:CoreData VS Realm璧尸。
下面給出一個(gè)查詢的比較:
// 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則簡(jiǎn)單的多:
// Realm
let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString);
let specimens = Specimen.objectsWithPredicate(predicate).arraySortedByProperty("name", ascending: true)
總結(jié)一下Realm對(duì)CoreData的優(yōu)勢(shì):
- 不需要架構(gòu)Context那種煩人的東西
- CoreData 是一個(gè)博大精深的技術(shù),不要妄想幾天之內(nèi)可以用的很溜熬拒。
- 支持 NSPredicate
- CoreData多個(gè)持久化文件很麻煩爷光,Realm輕松支持這個(gè)功能
劣勢(shì):
- 是會(huì)增加應(yīng)用大概1MB的體積。CoreData原生支持澎粟,不會(huì)增加App體積蛀序。
Realm大部分源碼公開(kāi)在github上:realm。項(xiàng)目在新建兩年多時(shí)間活烙,已經(jīng)得到開(kāi)源社區(qū)大量關(guān)注:
官方也承諾會(huì)持續(xù)解決用戶反饋的各種問(wèn)題徐裸。也可以直接在他們twitter上去@他們。
需要知道的一些問(wèn)題
其實(shí)我自己覺(jué)得這些是可以接受的問(wèn)題啸盏。技術(shù)很多時(shí)候就是權(quán)衡重贺,為了達(dá)到一些目的,總是要犧牲掉一些東西回懦。
- 所有的存儲(chǔ)對(duì)象需要繼承RealmObject
比如我現(xiàn)在的項(xiàng)目的數(shù)據(jù)從網(wǎng)絡(luò)請(qǐng)求回來(lái)都會(huì)繼承自己寫的一個(gè)方便解析的基類气笙,在這里就需要做出一些適應(yīng)。
但是該問(wèn)題在swift中是不存在的粉怕。因?yàn)閟wift是天生的面向協(xié)議編程范式健民。
- 不能自定義getter、setter
realm會(huì)自動(dòng)生成getter贫贝、setter秉犹,如果自定義getter蛉谜、setter存儲(chǔ)就會(huì)有影響。如果要規(guī)避這個(gè)問(wèn)題崇堵,可以通過(guò)設(shè)置這個(gè)對(duì)象的忽略屬性型诚。
比如有個(gè)屬性id,需要自定義setter鸳劳≌幔可以在對(duì)象屬性里把id設(shè)置為忽略屬性,這樣realm就不會(huì)為它自動(dòng)生成getter赏廓、setter涵紊,但是也不會(huì)把id存入數(shù)據(jù)庫(kù)。接著自定義一個(gè)用于存儲(chǔ)的屬性比如realm_id幔摸。在id的setter中可以把把值也賦給realm_id摸柄。
這個(gè)問(wèn)題在swift中也是不存在的,因?yàn)閟wfit中使用的是willset既忆、didset這種通知機(jī)制驱负。
- 查詢的結(jié)果不是數(shù)組
為了能夠支持查詢結(jié)果的鏈?zhǔn)讲樵儯瑀ealm自定義了一個(gè)集合類型患雇≡炯梗可以枚舉,但是不是熟悉的數(shù)組了苛吱。又因?yàn)閞ealm的懶加載機(jī)制酪术,所以不建議在數(shù)據(jù)層把這個(gè)枚舉轉(zhuǎn)成數(shù)組類型。這樣的缺點(diǎn)就是上層知道了數(shù)據(jù)的存儲(chǔ)邏輯翠储。嚴(yán)格的說(shuō)這里不應(yīng)該讓上層知道拼缝。但是這樣設(shè)計(jì)也許是為了方便上層進(jìn)行再次檢索,realm有著優(yōu)越的查詢性能彰亥。