原創(chuàng):有趣知識(shí)點(diǎn)摸索型文章
創(chuàng)作不易,請(qǐng)珍惜,之后會(huì)持續(xù)更新,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈,這些文章記錄了我的IOS成長歷程,希望能與大家一起進(jìn)步
溫馨提示:由于簡書不支持目錄跳轉(zhuǎn),大家可通過command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容
目錄
- 一、Realm 是什么
- 1、Realm 到底是什么?
- 2、Realm 安裝
- 3、Realm 中的相關(guān)術(shù)語
- 二、如何使用 Realm
- 1、創(chuàng)建數(shù)據(jù)庫
- 2、建表
- 3、存儲(chǔ)數(shù)據(jù)
- 4、其他特性
- 5、注意事項(xiàng)
- 三、Realm 的缺點(diǎn)
一、Realm 是什么
1、Realm 到底是什么?
大家都知道Sqlite3
是一個(gè)移動(dòng)端上面使用的小型數(shù)據(jù)庫,FMDB
是基于Sqlite3
進(jìn)行的一個(gè)封裝。那Core Data
是數(shù)據(jù)庫么?Core Data
本身并不是數(shù)據(jù)庫,它是一個(gè)擁有多種功能的框架,其中一個(gè)重要的功能就是把應(yīng)用程序同數(shù)據(jù)庫之間的交互過程自動(dòng)化了。有了Core Data
框架以后,我們無須編寫SQL代碼,又可以是使用關(guān)系型數(shù)據(jù)庫。因?yàn)?code>Core Data會(huì)在底層自動(dòng)給我們生成應(yīng)該最佳優(yōu)化過的SQL語句。
那么Realm是數(shù)據(jù)庫么?Realm 不是 ORM
,也不基于 SQLite
創(chuàng)建,而是為移動(dòng)開發(fā)者定制的全功能數(shù)據(jù)庫。它可以將原生對(duì)象直接映射到Realm的數(shù)據(jù)庫引擎(遠(yuǎn)不僅是一個(gè)鍵值對(duì)存儲(chǔ))中。Realm 是一個(gè) MVCC
數(shù)據(jù)庫,底層是用 C++ 編寫的。MVCC
指的是多版本并發(fā)控制。
Realm是滿足ACID
的。原子性(Atomicity
)、一致性(Consistency
)、隔離性(Isolation
)、持久性(Durability
)。一個(gè)支持事務(wù)(Transaction
)的數(shù)據(jù)庫,必需要具有這四種特性。Realm都已經(jīng)滿足。
Realm 采用MVCC的設(shè)計(jì)思想
MVCC
解決了一個(gè)重要的并發(fā)問題:在所有的數(shù)據(jù)庫中都有這樣的時(shí)候,當(dāng)有人正在寫數(shù)據(jù)庫的時(shí)候有人又想讀取數(shù)據(jù)庫了(例如,不同的線程可以同時(shí)讀取或者寫入同一個(gè)數(shù)據(jù)庫)。這會(huì)導(dǎo)致數(shù)據(jù)的不一致性 - 可能當(dāng)你讀取記錄的時(shí)候一個(gè)寫操作才部分結(jié)束。
有很多的辦法可以解決讀、寫并發(fā)的問題,最常見的就是給數(shù)據(jù)庫加鎖。在之前的情況下,我們?cè)趯憯?shù)據(jù)的時(shí)候就會(huì)加上一個(gè)鎖。在寫操作完成之前,所有的讀操作都會(huì)被阻塞。這就是眾所周知的讀-寫鎖。這常常都會(huì)很慢。Realm采用的是MVCC
數(shù)據(jù)庫的優(yōu)點(diǎn)就展現(xiàn)出來了,速度非常快。
MVCC
在設(shè)計(jì)上采用了和 Git
一樣的源文件管理算法。你可以把 Realm 的內(nèi)部想象成一個(gè) Git,它也有分支和原子化的提交操作。這意味著你可能工作在許多分支上(數(shù)據(jù)庫的版本),但是你卻沒有一個(gè)完整的數(shù)據(jù)拷貝。Realm 和真正的 MVCC
數(shù)據(jù)庫還是有些不同的。一個(gè)像 Git
的真正的 MVCC
數(shù)據(jù)庫,你可以有成為版本樹上 HEAD
的多個(gè)候選者。而 Realm 在某個(gè)時(shí)刻只有一個(gè)寫操作,而且總是操作最新的版本 - 它不可以在老的版本上工作。
Realm底層是B+
樹實(shí)現(xiàn)的,在Realm團(tuán)隊(duì)開源的realm-core里面可以看到源碼,里面有用bpTree
,這是一個(gè)B+
樹的實(shí)現(xiàn)。B+
樹是一種樹數(shù)據(jù)結(jié)構(gòu),是一個(gè)n
叉樹,每個(gè)節(jié)點(diǎn)通常有多個(gè)孩子,一棵B+
樹包含根節(jié)點(diǎn)、內(nèi)部節(jié)點(diǎn)和葉子節(jié)點(diǎn)。根節(jié)點(diǎn)可能是一個(gè)葉子節(jié)點(diǎn),也可能是一個(gè)包含兩個(gè)或兩個(gè)以上孩子節(jié)點(diǎn)的節(jié)點(diǎn)。
B+ 樹通常用于數(shù)據(jù)庫和操作系統(tǒng)的文件系統(tǒng)中。NTFS, ReiserFS, NSS, XFS, JFS, ReFS
和BFS
等文件系統(tǒng)都在使用B+
樹作為元數(shù)據(jù)索引。B+
樹的特點(diǎn)是能夠保持?jǐn)?shù)據(jù)穩(wěn)定有序,其插入與修改擁有較穩(wěn)定的對(duì)數(shù)時(shí)間復(fù)雜度。B+
樹元素自底向上插入。
Realm會(huì)讓每一個(gè)連接的線程都會(huì)有數(shù)據(jù)在一個(gè)特定時(shí)刻的快照。這也是為什么能夠在上百個(gè)線程中做大量的操作并同時(shí)訪問數(shù)據(jù)庫,卻不會(huì)發(fā)生崩潰的原因。
下圖很好的展現(xiàn)了Realm的一次寫操作流程。這里分3個(gè)階段,階段一中,V1
指向根節(jié)點(diǎn)R
。在階段二中,準(zhǔn)備寫入操作,這個(gè)時(shí)候會(huì)有一個(gè)V2
節(jié)點(diǎn),指向新的R'
,并且新建一個(gè)分支出來,A'
和C'
。相應(yīng)的右孩子指向原來V1
指向的R
的右孩子。如果寫入操作失敗,就丟棄左邊這個(gè)分支。這樣的設(shè)計(jì)可以保證即使失敗,也僅僅只丟失最新數(shù)據(jù),而不會(huì)破壞整個(gè)數(shù)據(jù)庫。如果寫入成功,那么把原來的R,A,C
節(jié)點(diǎn)放入Garbage
中,于是就到了第三階段,寫入成功,變成了V2
指向根節(jié)點(diǎn)。
在這個(gè)寫入的過程中,第二階段是最關(guān)鍵的,寫入操作并不會(huì)改變?cè)袛?shù)據(jù),而是新建了一個(gè)新的分支。這樣就不用加鎖,也可以解決數(shù)據(jù)庫的并發(fā)問題。正是B+
樹的底層數(shù)據(jù)結(jié)構(gòu) + MVCC
的設(shè)計(jì),保證了Realm的高性能。
Realm 采用了 zero-copy 架構(gòu)
因?yàn)?Realm 采用了 zero-copy
架構(gòu),這樣幾乎就沒有內(nèi)存開銷。這是因?yàn)槊恳粋€(gè) Realm 對(duì)象直接通過一個(gè)本地 long
指針和底層數(shù)據(jù)庫對(duì)應(yīng),這個(gè)指針是數(shù)據(jù)庫中數(shù)據(jù)的鉤子。
通常的傳統(tǒng)的數(shù)據(jù)庫操作是這樣的,數(shù)據(jù)存儲(chǔ)在磁盤的數(shù)據(jù)庫文件中,我們的查詢請(qǐng)求會(huì)轉(zhuǎn)換為一系列的SQL
語句,創(chuàng)建一個(gè)數(shù)據(jù)庫連接。數(shù)據(jù)庫服務(wù)器收到請(qǐng)求,通過解析器對(duì)SQL
語句進(jìn)行詞法和語法語義分析,然后通過查詢優(yōu)化器對(duì)SQL
語句進(jìn)行優(yōu)化,優(yōu)化完成執(zhí)行對(duì)應(yīng)的查詢,讀取磁盤的數(shù)據(jù)庫文件(有索引則先讀索引),讀取命中查詢的每一行的數(shù)據(jù),然后存到內(nèi)存里(這里有內(nèi)存消耗)。之后你需要把數(shù)據(jù)序列化成可在內(nèi)存里面存儲(chǔ)的格式,這意味著比特對(duì)齊,這樣 CPU 才能處理它們。最后,數(shù)據(jù)需要轉(zhuǎn)換成語言層面的類型,然后它會(huì)以對(duì)象的形式返回,比如Objective-C的對(duì)象等。
這里就是Realm另外一個(gè)很快的原因,Realm的數(shù)據(jù)庫文件是通過memory-mapped
,也就是說數(shù)據(jù)庫文件本身是映射到內(nèi)存(實(shí)際上是虛擬內(nèi)存)中的,Realm訪問文件偏移就好比文件已經(jīng)在內(nèi)存中一樣(這里的內(nèi)存是指虛擬內(nèi)存),它允許文件在沒有做反序列化的情況下直接從內(nèi)存讀取,提高了讀取效率。Realm 只需要簡單地計(jì)算偏移來找到文件中的數(shù)據(jù),然后從原始訪問點(diǎn)返回?cái)?shù)據(jù)結(jié)構(gòu)的值 。
正是Realm采用了 zero-copy
架構(gòu),幾乎沒有內(nèi)存開銷,Realm核心文件格式基于memory-mapped
,節(jié)約了大量的序列化和反序列化的開銷,導(dǎo)致了Realm獲取對(duì)象的速度特別高效。
Realm 對(duì)象在不同的線程間不能共享
Realm 對(duì)象不能在線程間傳遞的原因就是為了保證隔離性和數(shù)據(jù)一致性。這樣做的目的只有一個(gè),為了速度。由于Realm是基于零拷貝的,所有對(duì)象都在內(nèi)存里,所以會(huì)自動(dòng)更新。如果允許Realm對(duì)象在線程間共享,Realm 會(huì)無法確保數(shù)據(jù)的一致性,因?yàn)椴煌木€程會(huì)在不確定的什么時(shí)間點(diǎn)同時(shí)改變對(duì)象的數(shù)據(jù)。
要想保證多線程能共享對(duì)象就是加鎖,但是加鎖又會(huì)導(dǎo)致一個(gè)長時(shí)間的后臺(tái)寫事務(wù)會(huì)阻塞 UI 的讀事務(wù)。不加鎖就不能保證數(shù)據(jù)的一致性,但是可以滿足速度的要求。Realm在衡量之后,還是為了速度,做出了不允許線程間共享的妥協(xié)。正是因?yàn)椴辉试S對(duì)象在不同的線程間共享,保證了數(shù)據(jù)的一致性,不加線程鎖,保證了Realm的在速度上遙遙領(lǐng)先。
真正的懶加載
多數(shù)數(shù)據(jù)庫趨向于在水平層級(jí)存儲(chǔ)數(shù)據(jù),這也就是為什么你從 SQLite
讀取一個(gè)屬性的時(shí)候,你就必須要加載整行的數(shù)據(jù)。它在文件中是連續(xù)存儲(chǔ)的。不同的是,Realm盡可能讓 Realm 在垂直層級(jí)連續(xù)存儲(chǔ)屬性,你也可以看作是按列存儲(chǔ)。在查詢到一組數(shù)據(jù)后,只有當(dāng)你真正訪問對(duì)象的時(shí)候才真正加載進(jìn)來。
總結(jié)
經(jīng)過上面的分析之后,深深的感受到Realm就是為速度而生的!在保證了ACID
的要求下,很多設(shè)計(jì)都是以速度為主。當(dāng)然,Realm 最核心的理念就是對(duì)象驅(qū)動(dòng),這是 Realm 的核心原則。Realm 本質(zhì)上是一個(gè)嵌入式數(shù)據(jù)庫,但是它也是看待數(shù)據(jù)的另一種方式。它用另一種角度來重新看待移動(dòng)應(yīng)用中的模型和業(yè)務(wù)邏輯。Realm還是跨平臺(tái)的,多個(gè)平臺(tái)都使用相同的數(shù)據(jù)庫,是多么好的一件事情呀。相信使用Realm作為App數(shù)據(jù)庫的開發(fā)者會(huì)越來越多。
2、Realm 安裝
Realm是由Y Combinator孵化的創(chuàng)業(yè)團(tuán)隊(duì)開源出來的一款可以用于iOS(同樣適用于Swift&Objective-C)和Android的跨平臺(tái)移動(dòng)數(shù)據(jù)庫。Realm官網(wǎng)上說了好多優(yōu)點(diǎn),我覺得選用Realm的最吸引人的優(yōu)點(diǎn)就三點(diǎn):
跨平臺(tái):現(xiàn)在很多應(yīng)用都是要兼顧iOS和Android兩個(gè)平臺(tái)同時(shí)開發(fā)。如果兩個(gè)平臺(tái)都能使用相同的數(shù)據(jù)庫,那就不用考慮內(nèi)部數(shù)據(jù)的架構(gòu)不同,使用Realm
提供的API,可以使數(shù)據(jù)持久化層在兩個(gè)平臺(tái)上無差異化的轉(zhuǎn)換。
簡單易用:Core Data
和 SQLite
冗余、繁雜的知識(shí)和代碼足以嚇退絕大多數(shù)剛?cè)腴T的開發(fā)者,而換用 Realm,則可以極大地減少學(xué)習(xí)成本,立即學(xué)會(huì)本地化存儲(chǔ)的方法。毫不吹噓的說,把官方最新文檔完整看一遍,就完全可以上手開發(fā)了。
可視化:Realm 還提供了一個(gè)輕量級(jí)的數(shù)據(jù)庫查看工具,在Mac Appstore 可以下載Realm Browser
這個(gè)工具,開發(fā)者可以查看數(shù)據(jù)庫當(dāng)中的內(nèi)容,執(zhí)行簡單的插入和刪除數(shù)據(jù)的操作。畢竟,很多時(shí)候,開發(fā)者使用數(shù)據(jù)庫的理由是因?yàn)橐峁┮恍┧^的知識(shí)庫。Realm Browser
這個(gè)工具調(diào)試起Realm
數(shù)據(jù)庫實(shí)在太好用了,強(qiáng)烈推薦。
如果使用模擬器進(jìn)行調(diào)試,可以通過打印出Realm
數(shù)據(jù)庫地址,然后在Finder中??G
跳轉(zhuǎn)到對(duì)應(yīng)路徑下,用Realm Browser
打開對(duì)應(yīng)的.realm
文件就可以看到數(shù)據(jù)啦。如果是使用真機(jī)調(diào)試的話Xcode->Window->Devices
然后找到對(duì)應(yīng)的設(shè)備與項(xiàng)目,點(diǎn)擊Download Container
,導(dǎo)出xcappdata
文件后顯示包內(nèi)容,進(jìn)到AppData->Documents
,使用Realm Browser
打開.realm
文件即可。
在項(xiàng)目的Podfile
中,添加pod 'Realm'
,在終端運(yùn)行pod install
進(jìn)行安裝即可。這里如果是純的OC項(xiàng)目,就安裝OC的Realm,如果是純的Swift項(xiàng)目,就安裝Swift的Realm。如果是混編項(xiàng)目,就需要安裝OC的Realm,然后要把 Swift/RLMSupport.swift 文件一同編譯進(jìn)去。RLMSupport.swift
這個(gè)文件為 Objective-C 版本的 Realm
集合類型中引入了 Sequence
一致性,并且重新暴露了一些不能夠從 Swift 中進(jìn)行原生訪問的 Objective-C 方法,例如可變參數(shù) (variadic arguments
)。
3、Realm 中的相關(guān)術(shù)語
為了能更好的理解Realm
的使用,先介紹一下涉及到的相關(guān)術(shù)語。
RLMRealm:Realm是框架的核心所在,是我們構(gòu)建數(shù)據(jù)庫的訪問點(diǎn),就如同Core Data
的管理對(duì)象上下文(managed object context
)一樣。出于簡單起見,realm
提供了一個(gè)默認(rèn)的defaultRealm( )
的便利構(gòu)造器方法。
RLMObject:這是我們自定義的Realm
數(shù)據(jù)模型。創(chuàng)建數(shù)據(jù)模型的行為對(duì)應(yīng)的就是數(shù)據(jù)庫的結(jié)構(gòu)。要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)模型,我們只需要繼承RLMObject
,然后設(shè)計(jì)我們想要存儲(chǔ)的屬性即可。
關(guān)系(Relationships):通過簡單地在數(shù)據(jù)模型中聲明一個(gè)RLMObject
類型的屬性,我們就可以創(chuàng)建一個(gè)“一對(duì)多”的對(duì)象關(guān)系。同樣地,我們還可以創(chuàng)建“多對(duì)一”和“多對(duì)多”的關(guān)系。
寫操作事務(wù)(Write Transactions):數(shù)據(jù)庫中的所有操作,比如創(chuàng)建、編輯,或者刪除對(duì)象,都必須在事務(wù)中完成。“事務(wù)”是指位于write
閉包內(nèi)的代碼段。
查詢(Queries):要在數(shù)據(jù)庫中檢索信息,我們需要用到“檢索”操作。檢索最簡單的形式是對(duì)Realm( )
數(shù)據(jù)庫發(fā)送查詢消息。如果需要檢索更復(fù)雜的數(shù)據(jù),那么還可以使用斷言(predicates
)、復(fù)合查詢以及結(jié)果排序等等操作。
RLMResults:這個(gè)類是執(zhí)行任何查詢請(qǐng)求后所返回的類,其中包含了一系列的RLMObject
對(duì)象。RLMResults
和NSArray
類似,我們可以用下標(biāo)語法來對(duì)其進(jìn)行訪問,并且還可以決定它們之間的關(guān)系。不僅如此,它還擁有許多更強(qiáng)大的功能,包括排序、查找等等操作。
二、如何使用 Realm
由于Realm的API極為友好,一看就懂,所以這里就按照平時(shí)開發(fā)的順序,把需要用到的都梳理一遍。
1、創(chuàng)建數(shù)據(jù)庫
- (void)creatDataBaseWithName:(NSString *)databaseName
{
NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [docPath objectAtIndex:0];
NSString *filePath = [path stringByAppendingPathComponent:databaseName];
NSLog(@"數(shù)據(jù)庫目錄 = %@",filePath);
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [NSURL URLWithString:filePath];
config.objectClasses = @[MyClass.class, MyOtherClass.class];
config.readOnly = NO;
int currentVersion = 1.0;
config.schemaVersion = currentVersion;
config.migrationBlock = ^(RLMMigration *migration , uint64_t oldSchemaVersion) {
// 這里是設(shè)置數(shù)據(jù)遷移的block
if (oldSchemaVersion < currentVersion) {
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
}
創(chuàng)建數(shù)據(jù)庫主要設(shè)置RLMRealmConfiguration
,設(shè)置數(shù)據(jù)庫名字和存儲(chǔ)地方。把路徑以及數(shù)據(jù)庫名字拼接好字符串,賦值給fileURL
即可。readOnly
是控制是否只讀屬性。
objectClasses
這個(gè)屬性是用來控制對(duì)哪個(gè)類能夠存儲(chǔ)在指定 Realm 數(shù)據(jù)庫中做出限制。例如,如果有兩個(gè)團(tuán)隊(duì)分別負(fù)責(zé)開發(fā)您應(yīng)用中的不同部分,并且同時(shí)在應(yīng)用內(nèi)部使用了 Realm 數(shù)據(jù)庫,那么您肯定不希望為它們協(xié)調(diào)進(jìn)行數(shù)據(jù)遷移。您可以通過設(shè)置RLMRealmConfiguration
的 objectClasses
屬性來對(duì)類做出限制。objectClasses
一般可以不用設(shè)置。
還有一個(gè)很特殊的數(shù)據(jù)庫,內(nèi)存數(shù)據(jù)庫。通常情況下,Realm 數(shù)據(jù)庫是存儲(chǔ)在硬盤中的,但是您能夠通過設(shè)置inMemoryIdentifier
而不是設(shè)置RLMRealmConfiguration
中的fileURL
屬性,以創(chuàng)建一個(gè)完全在內(nèi)存中運(yùn)行的數(shù)據(jù)庫。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.inMemoryIdentifier = @"MyInMemoryRealm";
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
內(nèi)存數(shù)據(jù)庫在每次程序運(yùn)行期間都不會(huì)保存數(shù)據(jù)。但是,這不會(huì)妨礙到 Realm 的其他功能,包括查詢、關(guān)系以及線程安全。如果需要一種靈活的數(shù)據(jù)讀寫但又不想儲(chǔ)存數(shù)據(jù)的方式的話,那么可以選擇用內(nèi)存數(shù)據(jù)庫,內(nèi)存數(shù)據(jù)庫使用場景感覺不多。
使用內(nèi)存數(shù)據(jù)庫需要注意的是:內(nèi)存數(shù)據(jù)庫會(huì)在臨時(shí)文件夾中創(chuàng)建多個(gè)文件,用來協(xié)調(diào)處理諸如跨進(jìn)程通知之類的事務(wù)。 實(shí)際上沒有任何的數(shù)據(jù)會(huì)被寫入到這些文件當(dāng)中,除非操作系統(tǒng)由于內(nèi)存過滿, 需要清除磁盤上的多余空間。才會(huì)去把內(nèi)存里面的數(shù)據(jù)存入到文件中。
如果某個(gè)內(nèi)存 Realm 數(shù)據(jù)庫實(shí)例沒有被引用,那么所有的數(shù)據(jù)就會(huì)被釋放。所以必須要在應(yīng)用的生命周期內(nèi)保持對(duì)Realm內(nèi)存數(shù)據(jù)庫的強(qiáng)引用,以避免數(shù)據(jù)丟失。
2、建表
Realm數(shù)據(jù)模型是基于標(biāo)準(zhǔn) Objective?C 類來進(jìn)行定義的,使用屬性來完成模型的具體定義。我們只需要繼承RLMObject
或者一個(gè)已經(jīng)存在的模型類,您就可以創(chuàng)建一個(gè)新的 Realm 數(shù)據(jù)模型對(duì)象。對(duì)應(yīng)在數(shù)據(jù)庫里面就是一張表。
注意,RLMObject
官方建議不要加上 Objective-C的property attributes
(如nonatomic, atomic, strong, copy, weak
等等)假如設(shè)置了,這些attributes
會(huì)一直生效直到RLMObject
被寫入realm
數(shù)據(jù)庫。
RLM_ARRAY_TYPE
宏創(chuàng)建了一個(gè)協(xié)議,從而允許 RLMArray
語法的使用。如果該宏沒有放置在模型接口的底部的話,您或許需要提前聲明該模型類。
@interface RLMUser : RLMObject
@property NSString *accid;
//用戶注冊(cè)id
@property NSInteger custId;
//姓名
@property NSString *custName;
//頭像大圖url
@property NSString *avatarBig;
@property RLMArray<Car> *cars;
RLM_ARRAY_TYPE(RLMUser) // 定義RLMArray<RLMUser>
@end
@interface Car : RLMObject
@property NSString *carName;
@property RLMUser *owner;
RLM_ARRAY_TYPE(Car) // 定義RLMArray<Car>
@end
關(guān)于RLMObject的的關(guān)系
這里可以類比Core Data里面xcdatamodel
文件里面那些箭頭。
對(duì)一(To-One)關(guān)系
對(duì)于多對(duì)一(many-to-one
)或者一對(duì)一(one-to-one
)關(guān)系來說,只需要聲明一個(gè)RLMObject
子類類型的屬性即可,如上面代碼例子,@property RLMUser *owner;
對(duì)多(To-Many)關(guān)系
通過 RLMArray
類型的屬性您可以定義一個(gè)對(duì)多關(guān)系。如上面代碼例子,@property RLMArray *cars;
反向關(guān)系(Inverse Relationship)
鏈接是單向性的。因此,如果對(duì)多關(guān)系屬性 RLMUser.cars
鏈接了一個(gè) Car
實(shí)例,而這個(gè)實(shí)例的對(duì)一關(guān)系屬性 Car.owner
又鏈接到了對(duì)應(yīng)的這個(gè) RLMUser
實(shí)例,那么實(shí)際上這些鏈接仍然是互相獨(dú)立的。
@interface Car : RLMObject
@property NSString *carName;
@property (readonly) RLMLinkingObjects *owners;
@end
@implementation Car
+ (NSDictionary *)linkingObjectsProperties {
return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:RLMUser.class propertyName:@"cars"],};
}
@end
還可以給RLMObject
設(shè)置主鍵primaryKey
,默認(rèn)值defaultPropertyValues
,忽略的屬性ignoredProperties
,必要屬性requiredProperties
,索引indexedProperties
。比較有用的是主鍵和索引。
@implementation Book
// 主鍵
+ (NSString *)primaryKey {
return @"ID";
}
//設(shè)置屬性默認(rèn)值
+ (NSDictionary *)defaultPropertyValues{
return @{@"carName":@"測(cè)試" };
}
//設(shè)置忽略屬性,即不存到realm數(shù)據(jù)庫中
+ (NSArray<NSString *> *)ignoredProperties {
return @[@"ID"];
}
//一般來說,屬性為nil的話realm會(huì)拋出異常,但是如果實(shí)現(xiàn)了這個(gè)方法的話,就只有name為nil會(huì)拋出異常,也就是說現(xiàn)在cover屬性可以為空了
+ (NSArray *)requiredProperties {
return @[@"name"];
}
//設(shè)置索引,可以加快檢索的速度
+ (NSArray *)indexedProperties {
return @[@"ID"];
}
@end
3、存儲(chǔ)數(shù)據(jù)
新建對(duì)象。注意,所有的必需屬性都必須在對(duì)象添加到 Realm 前被賦值。
// (1) 創(chuàng)建一個(gè)Car對(duì)象,然后設(shè)置其屬性
Car *car = [[Car alloc] init];
car.carName = @"Lamborghini";
// (2) 通過字典創(chuàng)建Car對(duì)象
Car *myOtherCar = [[Car alloc] initWithValue:@{@"name" : @"Rolls-Royce"}];
// (3) 通過數(shù)組創(chuàng)建Car對(duì)象
Car *myThirdcar = [[Car alloc] initWithValue:@[@"BMW"]];
增
請(qǐng)注意,如果在進(jìn)程中存在多個(gè)寫入操作的話,那么單個(gè)寫入操作將會(huì)阻塞其余的寫入操作,并且還會(huì)鎖定該操作所在的當(dāng)前線程。Realm這個(gè)特性與其他持久化解決方案類似,我們建議您使用該方案常規(guī)的最佳做法:將寫入操作轉(zhuǎn)移到一個(gè)獨(dú)立的線程中執(zhí)行。下面的代碼就是把寫事務(wù)放到子線程中去處理。
官方給出了一個(gè)建議:由于 Realm 采用了 MVCC 設(shè)計(jì)架構(gòu),讀取操作并不會(huì)因?yàn)閷懭胧聞?wù)正在進(jìn)行而受到影響。除非您需要立即使用多個(gè)線程來同時(shí)執(zhí)行寫入操作,不然您應(yīng)當(dāng)采用批量化的寫入事務(wù),而不是采用多次少量的寫入事務(wù)。
[realm beginWriteTransaction];
[realm addObject:Car];
[realm commitWriteTransaction];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject: Car];
}];
});
刪
[realm beginWriteTransaction];
// 刪除單條記錄
[realm deleteObject:Car];
// 刪除多條記錄
[realm deleteObjects:CarResult];
// 刪除所有記錄
[realm deleteAllObjects];
[realm commitWriteTransaction];
改
當(dāng)沒有主鍵的情況下,需要先查詢,再修改數(shù)據(jù)。 當(dāng)有主鍵的情況下,有以下幾個(gè)非常好用的API。addOrUpdateObject
會(huì)去先查找有沒有傳入的Car
相同的主鍵,如果有,就更新該條數(shù)據(jù)。這里需要注意,addOrUpdateObject
這個(gè)方法不是增量更新,所有的值都必須有,如果有哪幾個(gè)值是null
,那么就會(huì)覆蓋原來已經(jīng)有的值,這樣就會(huì)出現(xiàn)數(shù)據(jù)丟失的問題。createOrUpdateInRealm:withValue:
這個(gè)方法是增量更新的,后面?zhèn)饕粋€(gè)字典,使用這個(gè)方法的前提是有主鍵。方法會(huì)先去主鍵里面找有沒有字典里面?zhèn)魅氲闹麈I的記錄,如果有,就只更新字典里面的子集。如果沒有,就新建一條記錄。
[realm addOrUpdateObject:Car];
[Car createOrUpdateInRealm:realm withValue:@{@"id": @1, @"price": @9000.0f}];
查
在Realm中所有的查詢(包括查詢和屬性訪問)在 Realm 中都是延遲加載的,只有當(dāng)屬性被訪問時(shí),才能夠讀取相應(yīng)的數(shù)據(jù)。查詢結(jié)果并不是數(shù)據(jù)的拷貝:修改查詢結(jié)果(在寫入事務(wù)中)會(huì)直接修改硬盤上的數(shù)據(jù)。同樣地,您可以直接通過包含在RLMResults
中的RLMObject
對(duì)象完成遍歷關(guān)系圖的操作。除非查詢結(jié)果被使用,否則檢索的執(zhí)行將會(huì)被推遲。這意味著鏈接幾個(gè)不同的臨時(shí) {RLMResults
} 來進(jìn)行排序和匹配數(shù)據(jù),不會(huì)執(zhí)行額外的工作,例如處理中間狀態(tài)。 一旦檢索執(zhí)行之后,或者通知模塊被添加之后, RLMResults
將隨時(shí)保持更新,接收 Realm 中,在后臺(tái)線程上執(zhí)行的檢索操作中可能所做的更改。
//從默認(rèn)數(shù)據(jù)庫查詢所有的車
RLMResults<Car *> *cars = [Car allObjects];
// 使用斷言字符串查詢
RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = '棕黃色' AND name BEGINSWITH '大'"];
// 使用 NSPredicate 查詢
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
@"棕黃色", @"大"];
RLMResults *results = [Dog objectsWithPredicate:pred];
// 排序名字以“大”開頭的棕黃色狗狗
RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = '棕黃色' AND name BEGINSWITH '大'"] sortedResultsUsingProperty:@"name" ascending:YES];
Realm還能支持鏈?zhǔn)讲樵儭ealm 查詢引擎一個(gè)特性就是它能夠通過非常小的事務(wù)開銷來執(zhí)行鏈?zhǔn)讲樵?chain queries
),而不需要像傳統(tǒng)數(shù)據(jù)庫那樣為每個(gè)成功的查詢創(chuàng)建一個(gè)不同的數(shù)據(jù)庫服務(wù)器訪問。
RLMResults<Car *> *Cars = [Car objectsWhere:@"color = blue"];
RLMResults<Car *> *CarsWithBNames = [Cars objectsWhere:@"name BEGINSWITH 'B'"];
4、其他特性
支持KVC和KVO
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è)訪問器了。
Realm 對(duì)象的大多數(shù)屬性都遵從 KVO 機(jī)制。所有 RLMObject
子類的持久化(persisted
)存儲(chǔ)(未被忽略)的屬性都是遵循 KVO 機(jī)制的,并且 RLMObject
以及 RLMArray
中無效的(invalidated
)屬性也同樣遵循(然而 RLMLinkingObjects
屬性并不能使用 KVO 進(jìn)行觀察)。
RLMResults<Person *> *persons = [Person allObjects];
[[RLMRealm defaultRealm] transactionWithBlock:^{
[[persons firstObject] setValue:@YES forKeyPath:@"isFirst"]; // 將每個(gè)人的 planet 屬性設(shè)置為“地球”
[persons setValue:@"地球" forKeyPath:@"planet"];
}];
支持?jǐn)?shù)據(jù)庫加密
Realm 支持在創(chuàng)建 Realm 數(shù)據(jù)庫時(shí)采用64位的密鑰對(duì)數(shù)據(jù)庫文件進(jìn)行 AES-256+SHA2
加密。這樣硬盤上的數(shù)據(jù)都能都采用AES-256
來進(jìn)行加密和解密,并用 SHA-2 HMAC
來進(jìn)行驗(yàn)證。每次您要獲取一個(gè) Realm 實(shí)例時(shí),您都需要提供一次相同的密鑰。不過,加密過的 Realm 只會(huì)帶來很少的額外資源占用(通常最多只會(huì)比平常慢10%)。
// 產(chǎn)生隨機(jī)密鑰
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
// 打開加密文件
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.encryptionKey = key;
NSError *error = nil;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
// 如果密鑰錯(cuò)誤,error 會(huì)提示數(shù)據(jù)庫不可訪問
NSLog(@"Error opening realm: %@", error);
}
通知
Realm 實(shí)例將會(huì)在每次寫入事務(wù)提交后,給其他線程上的 Realm 實(shí)例發(fā)送通知。一般控制器如果想一直持有這個(gè)通知,就需要申請(qǐng)一個(gè)屬性strong
持有這個(gè)通知。
// 獲取 Realm 通知
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) {
[myViewController updateUI];
}];
[token stop];
// 移除通知
[realm removeNotification:self.token];
我們還能進(jìn)行更加細(xì)粒度的通知,用集合通知就可以做到。集合通知是異步觸發(fā)的,首先它會(huì)在初始結(jié)果出現(xiàn)的時(shí)候觸發(fā),隨后當(dāng)某個(gè)寫入事務(wù)改變了集合中的所有或者某個(gè)對(duì)象的時(shí)候,通知都會(huì)再次觸發(fā)。這些變化可以通過傳遞到通知閉包的 RLMCollectionChange
參數(shù)訪問到。這個(gè)對(duì)象當(dāng)中包含了受deletions
、insertions
和 modifications
狀態(tài)所影響的索引信息。
集合通知對(duì)于 RLMResults
、RLMArray
、RLMLinkingObjects
以及 RLMResults
這些衍生出來的集合來說,當(dāng)關(guān)系中的對(duì)象被添加或者刪除的時(shí)候,一樣也會(huì)觸發(fā)這個(gè)狀態(tài)變化。
- (void)viewDidLoad {
[super viewDidLoad];
// 觀察 RLMResults 通知
__weak typeof(self) weakSelf = self;
self.notificationToken = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *change, NSError *error) {
if (error) {
NSLog(@"Failed to open Realm on background worker: %@", error);
return;
}
UITableView *tableView = weakSelf.tableView;
// 對(duì)于變化信息來說,檢索的初次運(yùn)行將會(huì)傳遞 nil
if (!changes) {
[tableView reloadData];
return;
}
// 檢索結(jié)果被改變,因此將它們應(yīng)用到 UITableView 當(dāng)中
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}];
}
數(shù)據(jù)庫遷移
這是Realm的優(yōu)點(diǎn)之一,方便遷移。對(duì)比Core Data的數(shù)據(jù)遷移,實(shí)在是方便太多了。數(shù)據(jù)庫存儲(chǔ)方面的增刪改查應(yīng)該都沒有什么大問題,比較蛋疼的應(yīng)該就是數(shù)據(jù)遷移了。在版本迭代過程中,很可能會(huì)發(fā)生表的新增,刪除,或者表結(jié)構(gòu)的變化,如果新版本中不做數(shù)據(jù)遷移,用戶升級(jí)到新版,很可能就直接crash了。對(duì)比Core Data的數(shù)據(jù)遷移比較復(fù)雜,Realm的遷移實(shí)在太簡單了。
1.新增刪除表,Realm不需要做遷移
2.新增刪除字段,Realm不需要做遷移。Realm 會(huì)自行檢測(cè)新增和需要移除的屬性,然后自動(dòng)更新硬盤上的數(shù)據(jù)庫架構(gòu)。
舉個(gè)官方給的數(shù)據(jù)遷移的例子:在block
里面分別有3種遷移方式,第一種是合并字段的例子,第二種是增加新字段的例子,第三種是原字段重命名的例子。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 2;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion)
{
// enumerateObjects:block: 遍歷了存儲(chǔ)在 Realm 文件中的每一個(gè)“Person”對(duì)象
[migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) {
// 只有當(dāng) Realm 數(shù)據(jù)庫的架構(gòu)版本為 0 的時(shí)候,才添加 “fullName” 屬性
if (oldSchemaVersion < 1) {
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]];
}
// 只有當(dāng) Realm 數(shù)據(jù)庫的架構(gòu)版本為 0 或者 1 的時(shí)候,才添加“email”屬性
if (oldSchemaVersion < 2) {
newObject[@"email"] = @"";
}
// 替換屬性名
if (oldSchemaVersion < 3) { // 重命名操作應(yīng)該在調(diào)用 `enumerateObjects:` 之外完成
[migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"]; }
}];
};
[RLMRealmConfiguration setDefaultConfiguration:config];
// 現(xiàn)在我們已經(jīng)成功更新了架構(gòu)版本并且提供了遷移閉包,打開舊有的 Realm 數(shù)據(jù)庫會(huì)自動(dòng)執(zhí)行此數(shù)據(jù)遷移,然后成功進(jìn)行訪問
[RLMRealm defaultRealm];
5、注意事項(xiàng)
在我從0開始接觸Realm到熟練上手,基本就遇到了多線程這一個(gè)坑。可見Realm的API文檔是多么的友好。雖然坑不多,但是還有有些需要注意的地方。
跨線程訪問數(shù)據(jù)庫,Realm對(duì)象一定需要新建一個(gè)
如果程序崩潰了,出現(xiàn)以下錯(cuò)誤,那就是因?yàn)槟阍L問Realm數(shù)據(jù)的時(shí)候,使用的Realm對(duì)象所在的線程和當(dāng)前線程不一致。解決辦法就是在當(dāng)前線程重新獲取最新的Realm即可。
*** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'**
自己封裝一個(gè)Realm全局實(shí)例單例是沒啥作用的
這個(gè)也是我之前對(duì)Realm多線程理解不清,導(dǎo)致的一個(gè)誤解。很多開發(fā)者應(yīng)該都會(huì)對(duì)Core Data和Sqlite3或者FMDB,自己封裝一個(gè)類似Helper
的單例。于是我也在這里封裝了一個(gè)單例,在新建完Realm數(shù)據(jù)庫的時(shí)候strong
持有一個(gè)Realm的對(duì)象。然后之后的訪問中只需要讀取這個(gè)單例持有的Realm對(duì)象就可以拿到數(shù)據(jù)庫了。想法是好的,但是同一個(gè)Realm對(duì)象是不支持跨線程操作realm數(shù)據(jù)庫的。
Realm 通過確保每個(gè)線程始終擁有 Realm 的一個(gè)快照,以便讓并發(fā)運(yùn)行變得十分輕松。你可以同時(shí)有任意數(shù)目的線程訪問同一個(gè) Realm 文件,并且由于每個(gè)線程都有對(duì)應(yīng)的快照,因此線程之間絕不會(huì)產(chǎn)生影響。需要注意的一件事情就是不能讓多個(gè)線程都持有同一個(gè) Realm 對(duì)象的實(shí)例 。如果多個(gè)線程需要訪問同一個(gè)對(duì)象,那么它們分別會(huì)獲取自己所需要的實(shí)例(否則在一個(gè)線程上發(fā)生的更改就會(huì)造成其他線程得到不完整或者不一致的數(shù)據(jù))。
其實(shí)RLMRealm *realm = [RLMRealm defaultRealm];
這句話就是獲取了當(dāng)前realm
對(duì)象的一個(gè)實(shí)例,其實(shí)現(xiàn)就是拿到單例。所以我們每次在子線程里面不要再去讀取我們自己封裝持有的realm實(shí)例了,直接調(diào)用系統(tǒng)的這個(gè)方法即可,能保證訪問不出錯(cuò)。
transactionWithBlock 已經(jīng)處于一個(gè)寫的事務(wù)中,事務(wù)之間不能嵌套
transactionWithBlock
已經(jīng)處于一個(gè)寫的事務(wù)中,如果還在block
里面再寫一個(gè)commitWriteTransaction
,就會(huì)出錯(cuò),寫事務(wù)是不能嵌套的。
[realm transactionWithBlock:^{
[self.realm beginWriteTransaction];
[self convertToRLMUserWith:bhUser To:[self convertToRLMUserWith:bhUser To:nil]];
[self.realm commitWriteTransaction];
}];
出錯(cuò)信息如下:
*** Terminating app due to uncaught exception 'RLMException', reason: 'The Realm is already in a write transaction'**
建議每個(gè)model都需要設(shè)置主鍵,這樣可以方便add和update
如果能設(shè)置主鍵,請(qǐng)盡量設(shè)置主鍵,因?yàn)檫@樣方便我們更新數(shù)據(jù),我們可以很方便的調(diào)用addOrUpdateObject:
或者 createOrUpdateInRealm:withValue:
方法進(jìn)行更新。這樣就不需要先根據(jù)主鍵,查詢出數(shù)據(jù),然后再去更新。有了主鍵以后,這兩步操作可以一步完成。
查詢也不能跨線程查詢
RLMResults * results = [self selectUserWithAccid:bhUser.accid];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:results[0]];
}];
});
由于查詢是在子線程外查詢的,所以跨線程也會(huì)出錯(cuò),出錯(cuò)信息如下:
***** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread'**
三、Realm 的缺點(diǎn)
接下來請(qǐng)還在考慮是否使用Realm的同學(xué)仔細(xì)看清楚,下面是你需要權(quán)衡是否要換到Realm數(shù)據(jù)庫的重要標(biāo)準(zhǔn)。
從其他數(shù)據(jù)庫遷移到Realm
如果從其他數(shù)據(jù)庫遷移到Realm,簡單的提一下蛋疼的問題,由于切換了數(shù)據(jù)庫,需要在未來幾個(gè)版本都必須維護(hù)2套數(shù)據(jù)庫,因?yàn)槔嫌脩舻臄?shù)據(jù)需要慢慢從老數(shù)據(jù)庫遷移到Realm,這個(gè)有點(diǎn)蛋疼。遷移數(shù)據(jù)的那段代碼需要“惡心”的存在工程里。但是一旦都遷移完成,之后的路就比較平坦了。
當(dāng)然,如果是新的App,還在開發(fā)中,可以考慮直接使用Realm,會(huì)更爽。以上是第一道門檻,如果覺得遷移帶來的代價(jià)還能承受,那么恭喜你,已經(jīng)踏入Realm一半了。那么還請(qǐng)看第二道“門檻”。
Realm數(shù)據(jù)庫當(dāng)前版本的限制
NSData以及 NSString屬性不能保存超過 16 MB 大小的數(shù)據(jù)。如果要存儲(chǔ)大量的數(shù)據(jù),可通過將其分解為16MB 大小的塊,或者直接存儲(chǔ)在文件系統(tǒng)中,然后將文件路徑存儲(chǔ)在 Realm 中。如果您的應(yīng)用試圖存儲(chǔ)一個(gè)大于 16MB 的單一屬性,系統(tǒng)將在運(yùn)行時(shí)拋出異常。
盡管 Realm 文件可以被多個(gè)線程同時(shí)訪問,但是您不能跨線程處理 Realms、Realm 對(duì)象、查詢和查詢結(jié)果。這個(gè)其實(shí)也不算是個(gè)問題,我們?cè)诙嗑€程中新建新的Realm對(duì)象就可以解決。
Realm對(duì)象的 Setters & Getters
不能被重載。因?yàn)?Realm 在底層數(shù)據(jù)庫中重寫了 setters
和 getters
方法,所以您不可以在您的對(duì)象上再對(duì)其進(jìn)行重寫。一個(gè)簡單的替代方法就是:創(chuàng)建一個(gè)新的 Realm 忽略屬性,該屬性的訪問起可以被重寫, 并且可以調(diào)用其他的 getter
和 setter
方法。
一般來說 Realm 數(shù)據(jù)庫比 SQLite 數(shù)據(jù)庫在硬盤上占用的空間更少。如果您的 Realm 文件大小超出了您的想象,這可能是因?yàn)槟鷶?shù)據(jù)庫中的 RLMRealm
中包含了舊版本數(shù)據(jù)。 為了使您的數(shù)據(jù)有相同的顯示方式,Realm 只在循環(huán)迭代開始的時(shí)候才更新數(shù)據(jù)版本。這意味著,如果您從 Realm 讀取了一些數(shù)據(jù)并進(jìn)行了在一個(gè)鎖定的線程中進(jìn)行長時(shí)間的運(yùn)行,然后在其他線程進(jìn)行讀寫 Realm 數(shù)據(jù)庫的話,那么版本將不會(huì)被更新,Realm 將保存中間版本的數(shù)據(jù),但是這些數(shù)據(jù)已經(jīng)沒有用了,這導(dǎo)致了文件大小的增長。這部分空間會(huì)在下次寫入操作時(shí)被重復(fù)利用。這些操作可以通過調(diào)用writeCopyToPath:error:
來實(shí)現(xiàn)。解決辦法: 通過調(diào)用invalidate
,來告訴 Realm 您不再需要那些拷貝到 Realm 的數(shù)據(jù)了。這可以使我們不必跟蹤這些對(duì)象的中間版本。在下次出現(xiàn)新版本時(shí),再進(jìn)行版本更新。 您可能在 Realm 使用Grand Central Dispatch
時(shí)也發(fā)現(xiàn)了這個(gè)問題。在dispatch
結(jié)束后自動(dòng)釋放調(diào)度隊(duì)列(dispatch queue
)時(shí),調(diào)度隊(duì)列(dispatch queue
)沒有隨著程序釋放。這造成了直到 RLMRealm
對(duì)象被釋放后,Realm 中間版本的數(shù)據(jù)空間才會(huì)被再利用。為了避免這個(gè)問題,您應(yīng)該在 dispatch
隊(duì)列中,使用一個(gè)顯式的自動(dòng)調(diào)度隊(duì)列(dispatch queue
)。
Realm 沒有線程/進(jìn)程安全的自動(dòng)增長屬性機(jī)制,這在其他數(shù)據(jù)庫中常常用來產(chǎn)生主鍵。然而,在絕大多數(shù)情況下,對(duì)于主鍵來說,我們需要的是一個(gè)唯一的、自動(dòng)生成的值,因此沒有必要使用順序的、連續(xù)的、整數(shù)的 ID 作為主鍵。解決辦法:在這種情況下,一個(gè)獨(dú)一無二的字符串主鍵通常就能滿足需求了。一個(gè)常見的模式是將默認(rèn)的屬性值設(shè)置為 [[NSUUID UUID] UUIDString]
以產(chǎn)生一個(gè)唯一的字符串 ID。 自動(dòng)增長屬性另一種常見的動(dòng)機(jī)是為了維持插入之后的順序。在某些情況下,這可以通過向某個(gè) RLMArray
中添加對(duì)象,或者使用 [NSDate date]
默認(rèn)值的createdAt
屬性。
所有的數(shù)據(jù)模型必須直接繼承自RealmObject
。這阻礙我們利用數(shù)據(jù)模型中的任意類型的繼承。這一點(diǎn)也不算問題,我們只要自己再建立一個(gè)model
就可以解決這個(gè)問題。自己建立的model
可以自己隨意去繼承,這個(gè)model
專門用來接收網(wǎng)絡(luò)數(shù)據(jù),然后把自己的這個(gè)model
轉(zhuǎn)換成要存儲(chǔ)到表里面的model
,即RLMObject
對(duì)象。這樣這個(gè)問題也可以解決了。
Realm不支持集合類型。這一點(diǎn)也是比較蛋疼。Realm支持以下的屬性類型:BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData
。CGFloat
屬性的支持被取消了,因?yàn)樗痪邆淦脚_(tái)獨(dú)立性。這里就是不支持集合,比如說NSArray,NSMutableArray,NSDictionary,NSMutableDictionary,NSSet,NSMutableSet
。如果服務(wù)器傳來的一個(gè)字典,key是一個(gè)字符串,對(duì)應(yīng)的value
就是一個(gè)數(shù)組,這時(shí)候就想存儲(chǔ)這個(gè)數(shù)組就比較困難了。當(dāng)然Realm里面是有集合的,就是RLMArray
,這里面裝的都是RLMObject
。所以我們想解決這個(gè)問題,就需要把數(shù)據(jù)里面的東西都取出來,如果是model
,就先自己接收一下,然后轉(zhuǎn)換成RLMObject
的model
,再存儲(chǔ)到RLMArray
里面去,這樣轉(zhuǎn)換一遍,還是可以的做到的。