一:realm介紹
Realm是由Y Combinator公司孵化出來(lái)的一款可以用于iOS(同樣適用于Swift&Objective-C)和Android的跨平臺(tái)移動(dòng)數(shù)據(jù)庫(kù)月幌。歷經(jīng)幾年才打造出來(lái),為了徹底解決性能問(wèn)題,核心數(shù)據(jù)引擎用C++打造,并不是建立在SQLite之上的ORM遭顶,所以Realm相比SQLite和CoreData而言更快瞒爬、更好、更容易去使用和完成數(shù)據(jù)庫(kù)的操作花費(fèi)更少的代碼靠益。它旨在取代CoredData和sqlite,它不是對(duì)coreData的簡(jiǎn)單封裝、相反的残揉,Realm它使用了它自己的一套持久化存儲(chǔ)引擎胧后。而且Realm是完全免費(fèi)的,這不僅讓它變得更加的流行也使開(kāi)發(fā)者使用起來(lái)沒(méi)有任何限制抱环。
Realm是一個(gè)類(lèi)MVCC數(shù)據(jù)庫(kù)壳快,每個(gè)連接的線程在特定的時(shí)刻都有一個(gè)數(shù)據(jù)庫(kù)的快照。MVCC(Multi-Version Concurrent Control 多版本并發(fā)控制)在設(shè)計(jì)上采用了和Git一樣的源文件管理算法镇草,也就是說(shuō)你的每個(gè)連接線程就好比在一個(gè)分支(也就是數(shù)據(jù)庫(kù)的快照)上工作眶痰,但是你并沒(méi)有得到一個(gè)完整的數(shù)據(jù)庫(kù)拷貝。Realm和一些真正的MVCC數(shù)據(jù)庫(kù)如MySQL是不同的梯啤,Real在某個(gè)時(shí)刻只能有一個(gè)寫(xiě)操作竖伯,且總是操作最新的數(shù)據(jù)版本,不能在老版本操作因宇。
Realm數(shù)據(jù)庫(kù)使用了零拷貝技術(shù)七婴,這是與CoreData及其他數(shù)據(jù)庫(kù)完全不同的地方。
通常的數(shù)據(jù)庫(kù)操作是這樣的察滑,數(shù)據(jù)存儲(chǔ)在磁盤(pán)的數(shù)據(jù)庫(kù)文件中打厘,我們的查詢請(qǐng)求會(huì)轉(zhuǎn)換為一系列的SQL語(yǔ)句,創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接贺辰。數(shù)據(jù)庫(kù)服務(wù)器收到請(qǐng)求户盯,通過(guò)解析器對(duì)SQL語(yǔ)句進(jìn)行詞法和語(yǔ)法語(yǔ)義分析嵌施,然后通過(guò)查詢優(yōu)化器對(duì)SQL語(yǔ)句進(jìn)行優(yōu)化,優(yōu)化完成執(zhí)行對(duì)應(yīng)的查詢莽鸭,讀取磁盤(pán)的數(shù)據(jù)庫(kù)文件(有索引則先讀索引)吗伤,返回對(duì)應(yīng)的數(shù)據(jù)內(nèi)容并存儲(chǔ)到內(nèi)存中,數(shù)據(jù)還需要序列化成內(nèi)存可存儲(chǔ)的格式硫眨,最后數(shù)據(jù)還要轉(zhuǎn)換成語(yǔ)言層面的類(lèi)型足淆,比如Objective-C的對(duì)象等。
而Realm完全不同捺球,它的數(shù)據(jù)庫(kù)文件是通過(guò)memory-mapped缸浦,也就是說(shuō)數(shù)據(jù)庫(kù)文件本身是映射到內(nèi)存中的,Realm訪問(wèn)文件偏移就好比文件已經(jīng)在內(nèi)存中一樣(這里的內(nèi)存是指虛擬內(nèi)存)氮兵,它允許文件在沒(méi)有做反序列化的情況下直接從內(nèi)存讀取裂逐,提高了讀取效率。
二:Realm的特點(diǎn)
Realm以難以令人置信的快速和易用讓開(kāi)發(fā)者能夠用僅僅幾行代碼完成你所需要的一切功能泣栈。它旨在打造讓用戶得在到移動(dòng)領(lǐng)域離線時(shí)的最好體驗(yàn)卜高,我整理了Realm具有的如下特點(diǎn):
易安裝:正如你在將要看到的使用Realm工作。安裝Realm就像你想象中一樣簡(jiǎn)單南片。在Cocoapods中使用簡(jiǎn)單命令掺涛,你就可以使用Realm工作。
速度上:Realm是令人無(wú)法想象的快速使用數(shù)據(jù)庫(kù)工作的庫(kù)疼进。Realm比SQLite和CoreData更快薪缆,這里的數(shù)據(jù)就是最好的證明。
跨平臺(tái):Realm數(shù)據(jù)庫(kù)文件能夠跨平臺(tái)和可以同時(shí)在iOS和Andriod使用伞广。無(wú)論你是使用Java, Objective-C, or Swift拣帽,你都可以使用你的高級(jí)模型。
可擴(kuò)展性:在開(kāi)發(fā)你的移動(dòng)App特別是如果你的應(yīng)用程序涉及到大量的用戶和大量的記錄時(shí)嚼锄,具有良好的可擴(kuò)展性是非常重要的减拭。
好的文檔&支持:Realm團(tuán)隊(duì)提供了可讀的,非常有組織的并且豐富的文檔区丑。如果你遇到什么問(wèn)題通過(guò)Twitter拧粪、GitHub或者Stackoverflow與他們交流。(中文api文檔地址 https://realm.io/cn/docs/objc/latest/#primary-keys)
可靠性:Realm已經(jīng)被巨頭公司使用在他們的移動(dòng)App中沧侥,像Pinterest, Dubsmash, and Hipmunk可霎。
免費(fèi)性:使用Realm的所有功能都是免費(fèi)的。
懶加載:只有當(dāng)你真正訪問(wèn)對(duì)象的值時(shí)候才真正從磁盤(pán)中加載進(jìn)來(lái)正什。
三:realm與其他數(shù)據(jù)庫(kù)的效率比較
Realm的消息通知啥纸、數(shù)據(jù)加密、JSON支持等特性讓Realm直接區(qū)別于SQLite和CoreData婴氮。也為我們切換到Realm提供了理由支持斯棒。在性能上面根據(jù)Realm的測(cè)試其高于SQLite兩倍多,甩CoreData更不止一個(gè)數(shù)量級(jí)主经。
四:realm用法詳細(xì)介紹
一:安裝
利用cocopods安裝
1.把pod "Realm"添加到你的Podfile中荣暮。
2.在命令行中執(zhí)行pod install。
3.將CocoaPods生成的.xcworkspace運(yùn)用到你的開(kāi)發(fā)項(xiàng)目中即可罩驻。
二:創(chuàng)建數(shù)據(jù)庫(kù)
1:最簡(jiǎn)單的方式 使用默認(rèn)數(shù)據(jù)庫(kù)
RLMRealm *realm = [RLMRealm defaultRealm];
2:自定義配置數(shù)據(jù)庫(kù)
下面是創(chuàng)建數(shù)據(jù)庫(kù)的config的詳細(xì)介紹
三:創(chuàng)建表
在realm里創(chuàng)建表其實(shí)就是創(chuàng)建一個(gè)模型對(duì)象穗酥,這個(gè)模型對(duì)象必須繼承自Realm Model Object, Realm Model Object顧名思義, 就是Realm 數(shù)據(jù)庫(kù)存儲(chǔ)的模型對(duì)象,
數(shù)據(jù)表中一對(duì)一(one-to-one)關(guān)系來(lái)說(shuō),只需要聲明一個(gè)RLMObject子類(lèi)類(lèi)型的屬性即可惠遏,就如上圖
通過(guò)RLMArray類(lèi)型的屬性您可以定義一個(gè)對(duì)多關(guān)系砾跃。RLMArray中可以包含簡(jiǎn)單類(lèi)型的RLMObject,其接口與NSMutableArray非常類(lèi)似节吮。
RLMArray可能會(huì)包含多個(gè)相同 Realm 對(duì)象的引用抽高,即便對(duì)象帶有主鍵也是如此。例如透绩,您或許會(huì)創(chuàng)建一個(gè)空的RLMArray翘骂,然后連續(xù)三次向其中插入同一個(gè)對(duì)象;當(dāng)使用 0帚豪、1碳竟、2 的索引來(lái)訪問(wèn)元素的時(shí)候,RLMArray將會(huì)返回對(duì)應(yīng)的對(duì)象狸臣,而所返回的這三個(gè)對(duì)象都是同一個(gè)對(duì)象莹桅。
RLM_ARRAY_TYPE(Dog) // 定義一個(gè) RLMArray 類(lèi)型
RLM_ARRAY_TYPE 宏創(chuàng)建了一個(gè)協(xié)議,從而允許 RLMArray 語(yǔ)法的使用烛亦。如果該宏沒(méi)有放置在模型接口的底部的話诈泼,你或許需要提前聲明該模型類(lèi)。如下圖所示
反向關(guān)系
鏈接是單向性的此洲。因此厂汗,如果對(duì)多關(guān)系屬性Person.dogs鏈接了一個(gè)Dog實(shí)例,而這個(gè)實(shí)例的對(duì)一關(guān)系屬性Dog.owner又鏈接到了對(duì)應(yīng)的這個(gè)Person實(shí)例呜师,那么實(shí)際上這些鏈接仍然是互相獨(dú)立的娶桦。為Person實(shí)例的dogs屬性添加一個(gè)新的Dog實(shí)例,并不會(huì)將這個(gè)Dog實(shí)例的owner屬性自動(dòng)設(shè)置為該P(yáng)erson汁汗。但是由于手動(dòng)同步雙向關(guān)系會(huì)很容易出錯(cuò)衷畦,并且這個(gè)操作還非常得復(fù)雜、冗余知牌,因此 Realm 提供了“鏈接對(duì)象 (linking objects)”屬性來(lái)表示這些反向關(guān)系祈争。
借助鏈接對(duì)象屬性,您可以通過(guò)指定的屬性來(lái)獲取所有鏈接到指定對(duì)象的對(duì)象角寸。例如菩混,一個(gè)Dog對(duì)象可以擁有一個(gè)名為owners的鏈接對(duì)象屬性忿墅,這個(gè)屬性中包含了某些Person對(duì)象,而這些Person對(duì)象在其dogs屬性中包含了這一個(gè)確定的Dog對(duì)象沮峡。您可以將owners屬性設(shè)置為RLMLinkingObjects類(lèi)型疚脐,然后重寫(xiě)+[RLMObject linkingObjectsProperties]來(lái)指明關(guān)系,說(shuō)明ownders中包含了Person模型對(duì)象邢疙。如上圖的RLMLinkingObjects ,重寫(xiě)+[RLMObject linkingObjectsProperties]如下圖
RLMObject常用接口方法
非空字段:requiredProperties
在用SQL描述表格的時(shí)候棍弄,我們經(jīng)常會(huì)給一些字段加上"NOT NULL"來(lái)修飾,表示其必須要有值疟游。這個(gè)時(shí)候只要實(shí)現(xiàn):+ (nonnull NSArray *)requiredProperties 返回一個(gè)成員名的數(shù)組就可以了呼畸。比如:
+ (NSArray *)requiredProperties {
return @[@"name"];
}
建立索引:indexedProperties
在使用SQL的時(shí)候,為了提高查詢效率颁虐,通常會(huì)為一些字段增減"Index"屬性蛮原,Realm也是支持這個(gè)特性的:? ? ?+ (nonnull NSArray *)indexedProperties;同樣返回一成員名的數(shù)據(jù)就可以了。比如:
+ (NSArray *)indexedProperties {
return @[@"name"];
}
默認(rèn)值:defaultPropertyValues
在使用SQL的時(shí)候聪廉,對(duì)于“NOT NULL”的變量瞬痘,通常還會(huì)給一個(gè)默認(rèn)值,Realm通過(guò)一個(gè)字典來(lái)支持:? ? ? ? ? ? ? ?+ (nullable NSDictionary *)defaultPropertyValues;這里返回的是一個(gè)字典板熊,字典的key為一個(gè)個(gè)屬性的名字框全,value為這個(gè)屬性的默認(rèn)值。比如:
+ (NSDictionary *)defaultPropertyValues {
return @{@"name" : @"Jim", @"age": @12};
}
主鍵:primaryKey
在二維表中干签,主鍵是個(gè)至關(guān)重要的屬性津辩,他表示了那個(gè)字段是可以唯一標(biāo)記一行記錄的,不可重復(fù)容劳。Realm也是支持這一的特性:
+ (nullable NSString *)primaryKey;
因?yàn)槭侵麈I喘沿,所以和上面不一樣,直接返回主鍵屬性的名字竭贩,而不是一個(gè)數(shù)組蚜印。比如:
+ (NSString *)primaryKey {
return @"id";
}
不用Realm托管:ignoredProperties
因?yàn)镽ealm通過(guò)一個(gè)結(jié)構(gòu)的成員來(lái)表示表中的各個(gè)鍵值,但是如果其中有一部分我們并不想其錄入DB怎么辦呢留量?比如在Persion中有個(gè)可以由age推導(dǎo)的年齡段(小孩窄赋、青年、成年)楼熄,這個(gè)信息因?yàn)楹蚢ge重復(fù)不用存儲(chǔ)到DB中忆绰。為此Realm支持可以不托管RLMObject中的部分成員:
+ (nullable NSArray *)ignoredProperties;
+ (NSArray *)ignoredProperties {
return @[@"ageLevel"];
}
這樣我們還可以自定義ageLevel的setter和getter,因?yàn)椴挥肦ealm托管可岂,所以也就需要自己加上strong/weak等修飾了
四:增(realm的增刪改查? ? ?比較簡(jiǎn)潔明了错敢,我在這里貼幾段代碼就可以表示了,詳細(xì)的可以看api文檔)
五:刪
六:改
1內(nèi)容直接更新
2 根據(jù)主鍵更新
如果您的數(shù)據(jù)模型中設(shè)置了主鍵的話缕粹,那么您可以使用+[RLMObject createOrUpdateInRealm:withValue:]來(lái)更新對(duì)象稚茅,或者當(dāng)對(duì)象不存在時(shí)插入新的對(duì)象
// 創(chuàng)建一個(gè)帶有主鍵的“書(shū)籍”對(duì)象纸淮,作為事先存儲(chǔ)的書(shū)籍
Book *cheeseBook = [[Book alloc] init];
cheeseBook.title = @"奶酪食譜";
cheeseBook.price = @9000;
cheeseBook.id = @1;
// 通過(guò) id = 1 更新該書(shū)籍
[realm beginWriteTransaction];
[Book createOrUpdateInRealm:realm withValue:cheeseBook];
[realm commitWriteTransaction];
七:查
// 查詢默認(rèn)的 Realm 數(shù)據(jù)庫(kù)
RLMResults *dogs = [Dog allObjects]; // 從默認(rèn)的 Realm 數(shù)據(jù)庫(kù)中,檢索所有狗狗
// 查詢指定的 Realm 數(shù)據(jù)庫(kù)
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 獲得一個(gè)指定的 Realm 數(shù)據(jù)庫(kù)
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 從該 Realm 數(shù)據(jù)庫(kù)中峰锁,檢索所有狗狗
條件查詢
1.使用斷言字符串查詢:
RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黃色' AND name BEGINSWITH '大'"];
2.// 使用 NSPredicate 查詢
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
@"棕黃色", @"大"];
RLMResults *tanDogs = [Dog objectsWithPredicate:pred];
3.鏈?zhǔn)讲樵?/p>
如果我們想獲得獲得棕黃色狗狗的查詢結(jié)果萎馅,并且在這個(gè)查詢結(jié)果的基礎(chǔ)上再獲得名字以“大”開(kāi)頭的棕黃色狗狗双戳。
RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黃色'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH '大'"];
關(guān)于查詢這塊 api文檔寫(xiě)的特別詳細(xì) 詳情可以去看api? (中文api鏈接:https://realm.io/cn/docs/objc/latest/#section-3)
八:數(shù)據(jù)排序
RLMResults 允許您指定一個(gè)排序標(biāo)準(zhǔn)虹蒋,從而可以根據(jù)一個(gè)或多個(gè)屬性進(jìn)行排序。比如說(shuō)飒货,下列代碼將上面例子中返回的狗狗根據(jù)名字升序進(jìn)行排序:
// 排序名字以“大”開(kāi)頭的棕黃色狗狗
RLMResults *sortedDogs = [[Dog objectsWhere:@"color = '棕黃色' AND name BEGINSWITH '大'"]
sortedResultsUsingProperty:@"name" ascending:YES];
五 :通知監(jiān)聽(tīng)魄衅、加密、KVC和KVO
1 RLMObject塘辅、RLMResult以及 RLMArray都遵守鍵值編碼(Key-Value Coding)(KVC)機(jī)制晃虫。當(dāng)您在運(yùn)行時(shí)才能決定哪個(gè)屬性需要更新的時(shí)候,這個(gè)方法是最有用的扣墩。將 KVC 應(yīng)用在集合當(dāng)中是大量更新對(duì)象的極佳方式哲银,這樣就可以不用經(jīng)常遍歷集合,為每個(gè)項(xiàng)目創(chuàng)建一個(gè)訪問(wèn)器了呻惕。
RLMResults *persons = [Person allObjects];[[RLMRealm defaultRealm] transactionWithBlock:^{ [[persons firstObject] setValue:@YES forKeyPath:@"isFirst"];// 將每個(gè)人的 planet 屬性設(shè)置為“地球”[persons setValue:@"地球"forKeyPath:@"planet"];}];
2 Realm 對(duì)象的大多數(shù)屬性都遵從 KVO 機(jī)制荆责。所有 RLMObject子類(lèi)的持久化(persisted)存儲(chǔ)(未被忽略)的屬性都是遵循 KVO 機(jī)制的,并且 RLMObject以及 RLMArray中 無(wú)效的(invalidated)屬性也同樣遵循(然而 RLMLinkingObjects屬性并不能使用 KVO 進(jìn)行觀察)亚脆。
3? realm支持?jǐn)?shù)據(jù)庫(kù)加密
// 產(chǎn)生隨機(jī)密鑰NSMutableData*key = [NSMutableDatadataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
// 打開(kāi)加密文件
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.encryptionKey = key;
NSError*error =nil;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if(!realm)?
{
// 如果密鑰錯(cuò)誤做院,`error` 會(huì)提示數(shù)據(jù)庫(kù)不可訪問(wèn)
NSLog(@"Error opening realm: %@", error);
}
Realm 支持在創(chuàng)建 Realm 數(shù)據(jù)庫(kù)時(shí)采用64位的密鑰對(duì)數(shù)據(jù)庫(kù)文件進(jìn)行 AES-256+SHA2 加密。這樣硬盤(pán)上的數(shù)據(jù)都能都采用AES-256來(lái)進(jìn)行加密和解密濒持,并用 SHA-2 HMAC 來(lái)進(jìn)行驗(yàn)證键耕。每次您要獲取一個(gè) Realm 實(shí)例時(shí),您都需要提供一次相同的密鑰柑营。不過(guò)屈雄,加密過(guò)的 Realm 只會(huì)帶來(lái)很少的額外資源占用(通常最多只會(huì)比平常慢10%)
4 通知
// 獲取 Realm 通知token = [realm addNotificationBlock:^(NSString*notification, RLMRealm * realm)
?{
?[myViewController updateUI];
}];
[token stop];// 移除通知[realm removeNotification:self.token];
Realm 實(shí)例將會(huì)在每次寫(xiě)入事務(wù)提交后,給其他線程上的 Realm 實(shí)例發(fā)送通知官套。一般控制器如果想一直持有這個(gè)通知酒奶,就需要申請(qǐng)一個(gè)屬性,strong持有這個(gè)通知虏杰。
六:錯(cuò)誤處理
?在數(shù)據(jù)的處理中可能會(huì)出現(xiàn)失敗的情況,在查看錯(cuò)誤的時(shí)候,有相關(guān)方法可以使用:
提交事務(wù)失敗
[realm commitWriteTransaction:(NSError * _Nullable __autoreleasing * _Nullable)]
也可以使用
[realm transactionWithBlock:^{
} error:(NSError * _Nullable __autoreleasing * _Nullable)]
配置數(shù)據(jù)庫(kù)失敗
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
要處理在指定線程中初次 Realm 數(shù)據(jù)庫(kù)導(dǎo)致的錯(cuò)誤讥蟆, 給 error 參數(shù)提供一個(gè) NSError 指針
七: 注意事項(xiàng)
1 跨線程訪問(wèn)數(shù)據(jù)庫(kù),realm一定要新建一個(gè)
Terminating app duetouncaught exception'RLMException', reason:'Realm accessed from incorrect thread.
如果出現(xiàn)上條錯(cuò)誤那就是因?yàn)槟阍L問(wèn)Realm數(shù)據(jù)的時(shí)候纺阔,使用的Realm對(duì)象所在的線程和當(dāng)前線程不一致瘸彤。
解決辦法就是在當(dāng)前線程重新獲取最新的Realm,即可笛钝。
2 自己封裝一個(gè)realm全局單例實(shí)例是沒(méi)啥用的
很多開(kāi)發(fā)者應(yīng)該都會(huì)對(duì)Core Data和Sqlite3或者FMDB质况,自己封裝一個(gè)類(lèi)似Helper的單例愕宋。于是我也在這里封裝了一個(gè)單例,在新建完Realm數(shù)據(jù)庫(kù)的時(shí)候strong持有一個(gè)Realm的對(duì)象结榄。然后之后的訪問(wèn)中只需要讀取這個(gè)單例持有的Realm對(duì)象就可以拿到數(shù)據(jù)庫(kù)了中贝。
想法是好的,但是同一個(gè)Realm對(duì)象是不支持跨線程操作realm數(shù)據(jù)庫(kù)的臼朗。
Realm 通過(guò)確保每個(gè)線程始終擁有 Realm 的一個(gè)快照邻寿,以便讓并發(fā)運(yùn)行變得十分輕松。你可以同時(shí)有任意數(shù)目的線程訪問(wèn)同一個(gè) Realm 文件视哑,并且由于每個(gè)線程都有對(duì)應(yīng)的快照绣否,因此線程之間絕不會(huì)產(chǎn)生影響。需要注意的一件事情就是不能讓多個(gè)線程都持有同一個(gè) Realm 對(duì)象的 實(shí)例 挡毅。如果多個(gè)線程需要訪問(wèn)同一個(gè)對(duì)象蒜撮,那么它們分別會(huì)獲取自己所需要的實(shí)例(否則在一個(gè)線程上發(fā)生的更改就會(huì)造成其他線程得到不完整或者不一致的數(shù)據(jù))。
其實(shí)RLMRealm *realm = [RLMRealm defaultRealm]; 這句話就是獲取了當(dāng)前realm對(duì)象的一個(gè)實(shí)例跪呈,其實(shí)實(shí)現(xiàn)就是拿到單例段磨。所以我們每次在子線程里面不要再去讀取我們自己封裝持有的realm實(shí)例了,直接調(diào)用系統(tǒng)的這個(gè)方法即可耗绿,能保證訪問(wèn)不出錯(cuò)苹支。
3 建議每個(gè)model 都設(shè)置主鍵,方便add和update
4查詢也不能跨線程
Terminating app duetouncaught exception'RLMException', reason:'Realm accessed from incorrect thread
處理方法是在當(dāng)前線程重新獲取最新的realm
5?Realm 沒(méi)有自動(dòng)增長(zhǎng)屬性