學(xué)習(xí)如何使用 Realm 數(shù)據(jù)庫引擎來輕松地實現(xiàn) Swift 的數(shù)據(jù)存儲
Realm 是一個跨平臺的移動數(shù)據(jù)庫引擎,于 2014 年 7 月發(fā)布,準(zhǔn)確來說,它是專門為移動應(yīng)用所設(shè)計的數(shù)據(jù)持久化解決方案之一。
Realm 可以輕松地移植到您的項目當(dāng)中,并且絕大部分常用的功能(比如說插入、查詢等等)都可以用一行簡單的代碼輕松完成!
Realm 并不是對 Core Data 的簡單封裝,相反地, Realm 并不是基于 Core Data ,也不是基于 SQLite 所構(gòu)建的。它擁有自己的數(shù)據(jù)庫存儲引擎,可以高效且快速地完成數(shù)據(jù)庫的構(gòu)建操作。
之前我們提到過,由于 Realm 使用的是自己的引擎,因此, Realm 就可以在 iOS 和 Android 平臺上共同使用(完全無縫),并且支持 Swift 、 Objective-C 以及 Java 語言來編寫( Android 平臺和 iOS 平臺使用不同的 SDK )。
數(shù)以萬計的使用 Realm 的開發(fā)者都會發(fā)現(xiàn),使用 Realm 比使用 SQLite 以及 Core Data 要快很多。下面我們給出一個例子,分別展示 Core Data 和 Realm 在執(zhí)行一個斷言查詢請求并且排序結(jié)果所使用的代碼量:
// 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 的簡單的:
// Realm let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString); let specimens = Specimen.objectsWithPredicate(predicate).arraySortedByProperty("name", ascending: true)
使用 Realm 可以讓代碼變得十分簡潔,從而讓您的代碼易讀易寫。
綜上所述,我們之所以使用 Realm 的理由不外乎如下幾點:
跨平臺 :現(xiàn)在絕大多數(shù)的應(yīng)用開發(fā)并不僅僅只在 iOS 平臺上進行開發(fā),還要兼顧到 Android 平臺的開發(fā)。為兩個平臺設(shè)計不同的數(shù)據(jù)庫是愚蠢的,而使用 Realm 數(shù)據(jù)庫, iOS 和 Android 無需考慮內(nèi)部數(shù)據(jù)的架構(gòu),調(diào)用 Realm 提供的 API 就可以完成數(shù)據(jù)的交換,實現(xiàn) “ 一個數(shù)據(jù)庫,兩個平臺無縫銜接 ” 。
簡單易用 : Core Data 和 SQLite 冗余、繁雜的知識和代碼足以嚇退絕大多數(shù)剛?cè)腴T的開發(fā)者,而換用 Realm ,則可以極大地減少學(xué)習(xí)代價和學(xué)習(xí)時間,讓應(yīng)用及早用上數(shù)據(jù)存儲功能。
可視化 : Realm 還提供了一個輕量級的數(shù)據(jù)庫查看工具,借助這個工具,開發(fā)者可以查看數(shù)據(jù)庫當(dāng)中的內(nèi)容,執(zhí)行簡單的插入和刪除數(shù)據(jù)的操作。畢竟,很多時候,開發(fā)者使用數(shù)據(jù)庫的理由是因為要提供一些所謂的 “ 知識庫 ” 。
本教程將會向您介紹 Realm 在 iOS 平臺上的簡單應(yīng)用,即導(dǎo)入 Realm 框架、創(chuàng)建數(shù)據(jù)模型、執(zhí)行查詢以及插入、更新和刪除記錄,以及使用既有的數(shù)據(jù)庫。
提示:原文教程寫于 2014 年,而 Realm 的版本更新得十分快,因此,本教程并不會拘泥于原文教程所述內(nèi)容,而是根據(jù) Realm 的版本更新進行相關(guān)修改。
原文作者提到,要在 Realm 抵達 1.0 版本的時候再來更新這篇教程,大家盡請期待吧!
讓我們開始吧
我們將會以一個實際的項目來進行教程:假設(shè)您在西雙版納自然保護區(qū)覓得了一份職位 “ 監(jiān)測員 ” ,職責(zé)是記錄這個 “ 動植物王國 ” 當(dāng)中所發(fā)現(xiàn)物種的相關(guān)信息,包括種群數(shù)量、發(fā)現(xiàn)區(qū)域、年齡結(jié)構(gòu)等等。因此,您需要一個助手來幫忙記錄您的發(fā)現(xiàn),但是很可惜的是,保護區(qū)并沒有多余的人手來做您的助手(主要是沒錢)。所以沒有辦法,我們必須為自己制作一個虛擬的 “ 助手 ” ,也就是一個以 “ 物種監(jiān)測 ” 命名的 APP ,這樣就可以隨手記錄我們的發(fā)現(xiàn)了!
在 Xcode 當(dāng)中打開我們的起始項目。此時, MapKit 已經(jīng)在項目當(dāng)中建立好了,而且項目已經(jīng)擁有了一些簡單的創(chuàng)建、更新和刪除物種信息的功能。
提示:如果您對 MapKit 的相關(guān)知識感興趣,可以查看 Introduction to MapKit tutorial ,這篇教程將會深入闡述 MapKit 是如何工作的。
現(xiàn)在,讓我們前往 Realm 的官網(wǎng)去下載 Realm 的框架吧: http://static.realm.io/downloads/cocoa/latest
Realm 的使用需求如下:
iOS ≥ 7 或者 Mac OS X ≥ 10.9
Xcode ≥ 6
現(xiàn)在 Realm 的版本為: 0.91.5
解壓下載下來的 Realm 壓縮包。在壓縮包中,我們可以看到一個名為 iOS 的文件夾。打開這個文件夾,然后將 Realm.framework 文件拖入到我們的起始項目中,最好拖放到 “Frameworks” 文件夾中以確保文件有序(強迫癥患者 ~ )。
將框架文件拖入到項目當(dāng)中
之后,一定要確保勾選了 Copy Items if needed 選項,然后單擊 Finish 按鈕就完成了往項目中添加框架的操作。
之后,定位到項目設(shè)置中 SISpeciesNotes 的 General 選項卡,然后在 Link Binary with Libraries 欄目中添加 libc++.dylib 動態(tài)庫文件。
然后回到解壓的 Realm 文件夾中,打開名為 Swift 的文件夾,然后將里面的 RLMSupport.swift 文件拖入到項目當(dāng)中。這個文件包含了用于 Realm 相關(guān)類的 Swift 簡便方法,比如說 RLMResults 中的 Generator 方法,這樣就可以像使用原生數(shù)組一樣使用 Realm 數(shù)組了。
好的,我們的準(zhǔn)備工作就完成了!您可以嘗試運行一下起始項目,以確保沒有任何錯誤產(chǎn)生。如果出現(xiàn)錯誤的話,請仔細查看上面所述的一些步驟,確保沒有任何疏漏發(fā)生。運行成功后的基本界面如下所示:
應(yīng)用界面
Realm Browser 介紹
Realm 資源包中包含了一個很有用的實用工具,可以幫助我們更好地管理 Realm 數(shù)據(jù)庫,那就是 Realm Browser 。
Realm Browser 可以讓您輕松地讀寫 Realm 數(shù)據(jù)庫(以 .realm 結(jié)尾),因此我們無需頭疼如何去查看 Realm 專有數(shù)據(jù)庫的邏輯結(jié)構(gòu)以及其中的數(shù)據(jù),可視化的操作就如同 SQLite 的其他數(shù)據(jù)庫查看工具一樣,十分簡單、易用(雖然 Realm Browser 的功能還十分簡陋,真的只能讀寫而已)。
Realm Browser
Realm Browser 可以在解壓的 Realm 文件夾中的 browser 文件夾中找到。您也可以訪問 Realm GitHub repository 然后在其中的 tools/RealmBrowser 目錄中找到它。
您可以嘗試在 Realm Browser 中選擇 Tools -> Generate demo database 來試著探索一下 Realm Browser 的功能。
Realm 相關(guān)術(shù)語和主要類
為了幫助您更好地理解 Realm 的使用,下面我們將會對 Realm 的相關(guān)術(shù)語和主要類進行一個大致的介紹:
RLMRealm : RLMRealm 是框架的核心所在,是我們構(gòu)建數(shù)據(jù)庫的訪問點,就如同 Core Data 的管理對象上下文( managed object context )一樣。出于簡單起見, realm 提供了一個名為 defaultRealm 的單例,在本教程中我們就僅使用這個單例來完成我們所需的功能。當(dāng)然,我們也可以導(dǎo)入外部已經(jīng)編寫好的 realm 數(shù)據(jù)庫文件,也可以在我們不需要將數(shù)據(jù)保存在硬盤上時使用 “ 內(nèi)存實例對象 ” ( in-memory realm instance ),此外,還可以同時使用多個數(shù)據(jù)庫文件。
RLMObject :這是我們自定義的 realm 數(shù)據(jù)模型。創(chuàng)建數(shù)據(jù)模型的行為將會影響到數(shù)據(jù)庫的結(jié)構(gòu)。要創(chuàng)建一個數(shù)據(jù)模型,我們只需要繼承 RLMObject ,然后設(shè)計我們想要存儲的屬性即可。
關(guān)系 (Relationships) :通過簡單地在數(shù)據(jù)模型中聲明一個 RLMObject 類型的屬性,我們就可以創(chuàng)建一個 “ 一對多 ” 的對象關(guān)系。同樣地,借助 RLMArray 我們還可以創(chuàng)建 “ 多對一 ” 和 “ 多對多 ” 的關(guān)系。
寫操作事務(wù) (Write Transactions) :數(shù)據(jù)庫中的所有操作,比如創(chuàng)建、編輯,或者刪除對象,都必須在事務(wù)中完成。 “ 事務(wù) ” 是指位于 beginWriteTransaction() 以及 commitWriteTransaction() 操作之間的代碼段。
查詢 (Queries) :要在數(shù)據(jù)庫中檢索信息,我們需要用到 “ 檢索 ” 操作。檢索最簡單的形式是對 RLMObject 對象發(fā)送 allObjects() 消息。如果需要檢索更復(fù)雜的數(shù)據(jù),那么還可以使用斷言( predicates )、復(fù)合查詢以及結(jié)果排序等等操作。
RLMResults :這個類是執(zhí)行任何查詢請求后所返回的類,其中包含了一系列的 RLMObjects 對象。和 NSArray 類似,我們可以用下標(biāo)語法來對其進行訪問,并且還可以決定它們之間的關(guān)系。不僅如此,它還擁有許多更強大的功能,包括排序、查找等等操作。
現(xiàn)在您應(yīng)該對 Realm 有了一個大概的了解了,現(xiàn)在是時候來試著使用 Realm 來完成起始項目的剩余工作了。
創(chuàng)建第一個數(shù)據(jù)模型
好了,前面我們廢話了這么多,現(xiàn)在終于要開始使用數(shù)據(jù)庫了。首先我們要創(chuàng)建一個數(shù)據(jù)模型,也相當(dāng)于創(chuàng)建數(shù)據(jù)庫的一個 “ 表 ” 。
右鍵選擇 Xcode 項目導(dǎo)航器中的 Model 組,然后選擇 New File -> iOS -> Source -> Swift File ,創(chuàng)建一個新的 swift 文件,將其命名為 SpeciesModel 并且確保選中了 SISpeciesNotes 對象。
提示:您也許查看過 Realm 的開發(fā)文檔,它里面介紹說可以使用 “ 插件 ” 來完成數(shù)據(jù)模型的簡單創(chuàng)建(也就是新建文件時,可以像新建 Core Data 數(shù)據(jù)模型文件一樣創(chuàng)建一個既定的模板數(shù)據(jù)模型),但是很遺憾的是,現(xiàn)在這個功能還只支持創(chuàng)建 OC 版本的數(shù)據(jù)模型文件,我們?yōu)榱舜a的 “ 干凈 ” ,就不采用這種方法。
打開 SpeciesModel.swift 文件,然后用以下代碼替換文件中的內(nèi)容:
import UIKit import Realm class SpeciesModel: RLMObject { dynamic var name = "" dynamic var speciesDescription = "" dynamic var latitude: Double = 0 dynamic var longitude: Double = 0 dynamic var created = NSDate() }
上面的代碼添加了一些屬性來存儲信息: name 屬性存儲物種名稱, speciesDescription 存儲物種的描述信息。對于 Realm 中的一些特定的數(shù)據(jù)類型,比如說字符串,必須要初始化。在本例中,我們使用空字符串來進行初始化。
latitude 以及 longitude 存儲了物種的經(jīng)緯度信息。在這里我們將其類型設(shè)置為 Double ( CLLocationDegrees 是 Double 的別名),并且使用 0 來進行初始化。
最后, created 存儲了這個物種所創(chuàng)建的時間信息。 NSDate() 將會返回當(dāng)前時間,因此我們就用這個值來初始化這個屬性
好了,現(xiàn)在我們就成功創(chuàng)建了第一個 Realm 數(shù)據(jù)模型了,要不要動動腦來完成一個小小的挑戰(zhàn)呢?
我們知道,這些物種將會被劃分為不同的 “ 類別 ” ,您的任務(wù)就是自行創(chuàng)建一個 “ 類別 ” 數(shù)據(jù)模型,這個文件將被命名為 CategoryModel.swift ,然后這個新的數(shù)據(jù)模型只要一個字符串類型的屬性 ——name 。
以下是解決方案的代碼:
import UIKit import Realm class CategoryModel: RLMObject { dynamic var name = "" }
我們現(xiàn)在擁有了 CategoryModel 數(shù)據(jù)模型了,下面我們將通過某種方式將其與 SpeciesModel 數(shù)據(jù)模型關(guān)聯(lián)起來,搭建起 “ 關(guān)系 ” 。
重新回顧一下上一節(jié)的內(nèi)容,我們可以通過簡單地聲明一個屬性來創(chuàng)建數(shù)據(jù)模型之間的關(guān)系。
打開 SpeciesModel.swift 文件,然后在 created 屬性下面添加如下語句:
dynamic var category = CategoryModel()
這個語句設(shè)置了 “ 物種 ” 和 “ 類別 ” 之間的 “ 一對多 ” 關(guān)系,這就意味著每個物種都只能夠擁有一個類別,但是一個類別可以從屬于多個物種。
好的,我們創(chuàng)建完了一個基礎(chǔ)數(shù)據(jù)模型了,現(xiàn)在是時候向數(shù)據(jù)庫中添加數(shù)據(jù)了!
添加數(shù)據(jù)
每當(dāng)用戶添加了一個新的物種標(biāo)記,用戶就可以對這個標(biāo)記進行修改,比如說設(shè)置物種名字,選擇類別等等。打開CategoriesTableViewController.swift 文件。這個視圖控制器將要在這個表視圖中顯示類別清單,以便用戶可以選擇。
因此,我們需要在應(yīng)用初始運行時,給用戶提供幾個默認的類別以供選擇。
在類定義當(dāng)中添加以下方法,別忘了在文件頂部導(dǎo)入 Realm 框架( import Realm ):
private func populateDefaultCategories() { self.results = CategoryModel.allObjects() // 1 if results.count == 0 { // 2 let realm = RLMRealm.defaultRealm() // 3 realm.beginWriteTransaction() // 4 let defaultCategories = Categories.allValues // 5 for category in defaultCategories { // 6 let newCategory = CategoryModel() newCategory.name = category realm.addObject(newCategory) } realm.commitWriteTransaction() // 7 self.results = CategoryModel.allObjects() } }
對應(yīng)的標(biāo)號注釋如下:
allobjects() 方法將會返回指定對象的所有元素,在本例中,我們向數(shù)據(jù)庫中的 CategoryModel 對象發(fā)送了一個查詢請求,返回這個表當(dāng)中的所有行信息。注意的是,這里我們得到的是一個 RLMResults 對象,這個對象用來存放我們的查詢結(jié)果。
如果查詢結(jié)果中的元素數(shù)量為 0 ,那么就說明數(shù)據(jù)庫當(dāng)中沒有類別信息的相關(guān)記錄,那么就意味著這是用戶第一次啟動應(yīng)用。
我們訪問默認的 realm 單例對象,然后將其用 realm 變量簡單表示,以供訪問
這一步將在默認 realm 數(shù)據(jù)庫中啟動一個事務(wù) —— 現(xiàn)在,我們就可以向數(shù)據(jù)庫當(dāng)中添加記錄了。
這里我們使用已經(jīng)定義過的 Categories 枚舉來創(chuàng)建一個含有全部默認類別的數(shù)組。
對于每個類別名稱來說,我們創(chuàng)建了一個對應(yīng)的 CategoryModel 實例對象,然后設(shè)置其 name 屬性,最后將這個對象添加到 realm 當(dāng)中。
當(dāng)我們添加完所有的類別之后,調(diào)用 commitWriteTransaction() 方法來關(guān)閉事務(wù),并且向數(shù)據(jù)庫提交數(shù)據(jù)。
只有調(diào)用了 commitWriteTransaction() 方法,我們之前做的所有關(guān)于事務(wù)的操作才能夠被成功運行,因為這涉及到 Realm 的內(nèi)部處理的問題了。您可以像上面我們做的那樣,執(zhí)行一些簡單的創(chuàng)建操作,或者您可以執(zhí)行一些復(fù)雜的操作,比如說同時創(chuàng)建、更新、刪除多個對象等等。
然后在 viewDidLoad() 方法的底部加入以下代碼:
populateDefaultCategories()
這個方法將會在視圖加載的過程中,添加我們的測試用類別,并且執(zhí)行向數(shù)據(jù)庫寫入數(shù)據(jù)的操作。
好了,現(xiàn)在我們的數(shù)據(jù)庫當(dāng)中已經(jīng)有了一些數(shù)據(jù)了,我們需要更新一下表試圖數(shù)據(jù)源相關(guān)方法,以顯示這些類別。找到 tableView(_:cellForRowAtIndexPath:) 方法,然后用以下代碼替換它:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("CategoryCell", forIndexPath: indexPath) as! UITableViewCell cell.textLabel?.text = (results[UInt(indexPath.row)] as! CategoryModel).name return cell }
這個聲明語句從 results 對象當(dāng)中讀取對應(yīng)行的名稱,然后設(shè)置到單元格的文本標(biāo)簽上面顯示。
接下來,添加一個新的屬性:
var selectedCategory: CategoryModel!
我們用這個屬性來存儲當(dāng)前選中的類別。
找到 tableView(_: willSelectedRowAtIndexPath:) ,然后用以下代碼替換它:
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? { selectedCategories = self.results[UInt(indexPath.row)] as! CategoryModel return indexPath }
上面聲明的方法將會在用戶點擊某個單元格的時候,將用戶點擊的類別存儲在 selectedCategory 屬性當(dāng)中。
編譯并運行這個應(yīng)用,然后嘗試定位到某個您感興趣的位置(使用模擬器的位置模擬),然后點擊右上角的 “+” 按鈕創(chuàng)建一個新的標(biāo)記點。點選地圖上的這個標(biāo)記點,然后點擊其彈出來的氣泡,接下來會彈出這個標(biāo)記點的詳細信息。隨后,點擊類別文本框,就可以看到如下圖所示的類別列表了:
類別列表
您可以選擇其中一個類別,不過這個操作僅僅只是將其保存到屬性當(dāng)中。如果您感興趣,可以前往模擬器的 Documents 目錄下面,使用 Realm Browser 查看我們生成的數(shù)據(jù)庫,在里面就可以看到我們寫入的數(shù)據(jù)了,這是不是很令人激動呢?
使用 Realm Browser
通常情況下,使用 defaultRealm() 方法生成的數(shù)據(jù)庫文件將會存放在 /Users/(Your Account)/Library/Developer/CoreSimulator/Devices/(Simulator ID)/data/Containers/Data/Application/(Application ID)/Documents/ 路徑下面,名為 default.realm 。 Simulator ID 指的是您運行的模擬器的 ID , Application ID 指的是這個應(yīng)用所分配到的 ID 。
如果您仍然不清楚這個 Realm 數(shù)據(jù)庫在哪兒的話,那么使用如下語句,就可以打印處這個數(shù)據(jù)庫所在的完整位置了:
println(RLMRealm.defaultRealm().path)
在這個 Documents 目錄下面,我們可能會看到兩個文件。一個是 default.realm 文件,這個是數(shù)據(jù)庫文件,里面就是數(shù)據(jù)的存放點了。而另一個則是 default.realm.lock 文件,這個文件也有可能不會存在,它是用來當(dāng)數(shù)據(jù)庫文件被使用時,防止其它應(yīng)用對其進行修改的一個文件。
雙擊這個 default.realm 文件,就可以使用 Realm Browser 打開了:
Realm Browser 打開的 default.realm 文件
注意:如果 default.realm 已經(jīng)在其它應(yīng)用中打開了,那么強行打開它就可能會出現(xiàn)異常。
.lock 文件就可以防止對 default.realm 文件的重復(fù)操作,在使用 Realm Browser 打開數(shù)據(jù)庫文件前,請先確保應(yīng)用沒有在運行,然后刪除 .lock 文件,才能打開。
一旦數(shù)據(jù)庫在 Realm Browser 中被打開,您將會看到 CategoryModel 類中擁有 6 個對象,這就意味著這個 “ 表 ” 中已經(jīng)存放了 6 個記錄了。點擊這個類就可以查看這個類當(dāng)中擁有的具體對象信息。
增加類別
好了,現(xiàn)在我們就可以來實現(xiàn) “ 為某個物種添加類別 ” 的功能了。
打開 AddNewEntryController.swift ,然后向類中添加以下屬性:
var selectedCategory: CategoryModel!
我們將會用這個屬性來存儲我們在 CategoriesTableViewController 選中的類別。
接下來,找到 unwindFromCategories(segue:) 方法,然后在方法底部添加以下代碼:
selectedCategory = categoriesController.selectedCategories categoryTextField.text = selectedCategory.name
這個方法會在用戶從 categoriesTableViewController 中選擇了一個類別后被調(diào)用。在這里,我們獲取到了這個選擇的類別,然后將其存儲在本地屬性 selectedCategory 當(dāng)中,接著,我們將它的值填充到文本框里面。
現(xiàn)在,我們已經(jīng)完成了類別的獲取,接下來就是要創(chuàng)建第一個物種了!
仍然還是在 AddNewEntryController.swift 當(dāng)中,向類中再添加一個屬性:
var species: SpeciesModel!
這個屬性將會存儲一個新的物種數(shù)據(jù)模型對象。
接下來,導(dǎo)入 Realm 框架,然后向類中添加以下方法:
func addNewSpecies() { let realm = RLMRealm.defaultRealm() // 1 realm.beginWriteTransaction() // 2 let newSpecies = SpeciesModel() // 3 // 4 newSpecies.name = nameTextField.text newSpecies.category = selectedCategory newSpecies.speciesDescription = descriptionTextView.text newSpecies.latitude = selectedAnnotation.coordinate.latitude newSpecies.longitude = selectedAnnotation.coordinate.longitude realm.addObject(newSpecies) // 5 realm.commitWriteTransaction() // 6 self.species = newSpecies }
對應(yīng)的標(biāo)號注釋如下:
獲取默認的 Realm 數(shù)據(jù)庫
開啟一個事務(wù)序列,準(zhǔn)備寫入數(shù)據(jù)
創(chuàng)建一個 Species 對象實例
接著,設(shè)置這個對象的相關(guān)值。這些值來自于用戶界面的文本輸入框。
向 realm 中寫入新的 Species 對象
最后,使用 commitWriteTransaction() 提交寫操作事務(wù)
在這里,我們需要使用 “ 輸入驗證 ” ,來確保用戶的輸入是正確的。在工程中已經(jīng)有了一個存在的 validateFields() 方法來執(zhí)行輸入驗證的工作,以確保物種名稱和描述不能為空。我們剛剛增加了設(shè)置類別的功能,那么我們應(yīng)該也要確保類別選擇不能為空。
在 validateFields() 方法中找到以下代碼:
if nameTextField.text.isEmpty || descriptionTextView.text.isEmpty {將其變更為:
if nameTextField.text.isEmpty || descriptionTextView.text.isEmpty || selectedCategory == nil {
這個方法經(jīng)能夠確保所有的文本框都有值,并且用戶也已經(jīng)選擇了一個類別。
接下來,向類中添加以下方法:
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool { if validateFields() { if species == nil { addNewSpecies() } return true } else { return false } }
在上面的代碼中,我們調(diào)用了輸入驗證的方法,如果所有文本框都有值的話,那么就可以添加一個新的物種。
編譯并運行您的應(yīng)用,單擊 “+” 按鈕來創(chuàng)建一個新的物種。然后輸入其名稱和描述,選擇一個類別,接著單擊 “ 保存 ” 按鈕來將這個物種添加到數(shù)據(jù)庫中。
添加新的數(shù)據(jù)
視圖消失了 —— 等等,怎么什么都沒有發(fā)生呢?什么情況?
哦對了,我們已經(jīng)向 Realm 數(shù)據(jù)庫提交了一個數(shù)據(jù),但是我們還沒有在地圖上做出相應(yīng)的設(shè)置和改變。
檢索數(shù)據(jù)
既然我們已經(jīng)向數(shù)據(jù)庫中添加了一個物種了,那么現(xiàn)在我們希望它能夠在地圖上顯示出來。
如果您想要檢視這個心數(shù)據(jù),那么打開 Realm Browser 就可以查看數(shù)據(jù)了。記住要先退出模擬器。
添加的物種信息
我們僅僅只能夠看見孤零零的一條記錄,里面存儲了記錄的名稱、描述信息、經(jīng)緯度信息、添加的時間。還有最重要的,就是我們看到了連接到 CategoryModel 的 category 記錄,這就意味著我們已經(jīng)創(chuàng)建好了物種和類別的 “ 一對多 ” 關(guān)系。點擊這個藍色的超鏈接,我們就可以查看 CategoryModel 的相關(guān)數(shù)據(jù)了。
好的,回到正題,我們現(xiàn)在需要在地圖上顯示新添加的數(shù)據(jù)。
打開 SpeciesAnnotation.swift ,然后向類中添加一個新的屬性:
var species: SpeciesModel?
這個屬性將會為這個標(biāo)記點保存它所擁有的物種信息。
接下來,用以下代碼替換構(gòu)造器:
init(coordinate: CLLocationCoordinate2D, title: String, sub: Categories, species: SpeciesModel? = nil) { self.coordinate = coordinate self.title = title self.subtitle = sub.rawValue self.species = species }
我們所做的改變,就是給這個構(gòu)造器方法添加了一個帶默認值的構(gòu)造器參數(shù),以便可以對 species 屬性進行賦值。默認值為 nil ,這意味著我們可以忽略這個參數(shù),使用前面三個參數(shù)進行初始化也是沒有任何問題的。
打開 MapViewController.swift ,然后向類中添加一個新屬性(同樣地,別忘了導(dǎo)入 Realm ):
var results: RLMResults?
如果我們想要在用屬性來存儲一系列物種,那么我們需要將這個屬性聲明為 RLMResults 類型。要記住,我們是不能夠初始化 RLMResults 對象的,我們必須要通過查詢操作來獲取它的值。
現(xiàn)在我們需要一些方法來獲取所有的物種數(shù)據(jù)。仍然還是在 MapViewController.swift 當(dāng)中,向類中添加如下方法:
func populateMap() { mapView.removeAnnotations(mapView.annotations) // 1 if let results = SpeciesModel.allObjects() { // 2 self.results = results for result in results { let species = result as! SpeciesModel let coordinate = CLLocationCoordinate2DMake(species.latitude, species.longitude) let speciesAnnotation = SpeciesAnnotation(coordinate: coordinate, title: species.name, sub: Categories(rawValue: species.category.name)!, species: species) // 3 mapView.addAnnotation(speciesAnnotation) // 4 } } }
對應(yīng)的標(biāo)號注釋如下:
首先,我們先清除了地圖上所有存在的標(biāo)記點,這樣我們就不用考慮其他的要素
然后,我們從 Realm 數(shù)據(jù)庫中獲取 Species 的全部數(shù)據(jù)
我們在此創(chuàng)建了一個自定義的 SpeciesAnnotation
最后,我們往 MKMapView 上添加這個標(biāo)記點
好的,現(xiàn)在我們可以在某處地方吊用這個方法了。找到 viewDidLoad() 然后將這個方法加入到這個方法底部:
populateMap()
這樣就確保了每當(dāng)?shù)貓D視圖控制器加載的時候,地圖就能夠顯示 Species 標(biāo)記點。
接著,我們僅需要修改標(biāo)記點的名稱和類別即可。找到 unwindFromAddNewEntry() ,然后使用下列代碼替換掉該方法:
@IBAction func unwindFromAddNewEntry(segue: UIStoryboardSegue) { let addNewEntryController = segue.sourceViewController as! AddNewEntryController let addedSpecies = addNewEntryController.species let addedSpeciesCoordinate = CLLocationCoordinate2DMake(addedSpecies.latitude, addedSpecies.longitude) if lastAnnotation != nil { mapView.removeAnnotation(lastAnnotation) } else { for annotation in mapView.annotations { let currentAnnotation = annotation as! SpeciesAnnotation if currentAnnotation.coordinate.latitude == addedSpeciesCoordinate.latitude && currentAnnotation.coordinate.longitude == addedSpeciesCoordinate.longitude { mapView.removeAnnotation(currentAnnotation) break } } } let annotation = SpeciesAnnotation(coordinate: addedSpeciesCoordinate, title: addedSpecies.name, sub: Categories(rawValue: addedSpecies.category.name)!, species: addedSpecies) mapView.addAnnotation(annotation) lastAnnotation = nil }
這個方法將會在我們從 AddNewEntryController 返回的時候被調(diào)用,然后這時候就會有一個新的物種被添加到地圖上方。當(dāng)我們添加了一個新的物種到地圖上,那么就會產(chǎn)生一個標(biāo)記圖標(biāo)。然后我們想要根據(jù)物種的類別來改變其圖標(biāo)的樣式,在這個代碼里面,我們就是簡單的移除了最后添加的這個標(biāo)記點,然后將其替換為有名稱和類別的標(biāo)記點。
編譯并運行您的應(yīng)用,創(chuàng)建一些不同的物種種類來查看現(xiàn)在地圖是什么樣式的吧!
添加的標(biāo)記點效果
另外一個視圖
您或許已經(jīng)注意到在地圖視圖的左上角有一個 “ 編輯 ” 的按鈕。為了更好地管理地圖上的記錄點,我們這個應(yīng)用設(shè)置了一個基于文本的表視圖,用來列出地圖上所有的記錄點,這個視圖我們現(xiàn)在命名為 “ 記錄 ” 視圖?,F(xiàn)在,這個表視圖仍然還是空的,現(xiàn)在我們就來向里面填充數(shù)據(jù)吧!
打開 LogViewController.swift ,然后將 species 屬性替換成以下形式(同樣地,要導(dǎo)入 Realm ):
var species: RLMResults!
在上面的代碼中,我們用 RLMResults 替換掉了之前的一個空數(shù)組占位符,這個操作和我們在 MapViewController 所做的一樣。
接下來,找到 viewDidLoad() 方法,然后在 super.viewDidLoad() 語句下添加以下代碼:
species = SpeciesModel.allObjects().sortedResultsUsingProperty("name", ascending: true)
這行代碼會將數(shù)據(jù)庫中的所有物種全部輸出到 species 當(dāng)中,并且按照名字進行排列。
接下來,用以下代碼替換 tableView(_:cellForRowAtIndexPath:) :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as! LogCell var speciesModel: SpeciesModel! speciesModel = species[UInt(indexPath.row)] as! SpeciesModel cell.titleLabel.text = speciesModel.name cell.subtitleLabel.text = speciesModel.category.name cell.iconImageView.image = getImageOfSpecies(speciesModel.category.name) return cell }
這個方法將會展示物種的名字和物種的類別,以及其圖標(biāo)。
編譯并運行應(yīng)用,單擊左上角的 “ 編輯 ” 按鈕,然后您就會在表視圖中看到我們之前錄入的物種信息,如圖所示:
記錄界面
刪除記錄
現(xiàn)在我們已經(jīng)學(xué)習(xí)了如何在 Realm 中創(chuàng)建記錄數(shù)據(jù),但是如果我們不小心添加了錯誤的標(biāo)記點,或者想要移除之前添加過的物種數(shù)據(jù),那么我們應(yīng)該要怎么做呢?因此,我們就需要添加從 Realm 中刪除數(shù)據(jù)的功能。您會發(fā)現(xiàn)這是一個非常簡單的操作。
打開 LogViewController.swift 文件,然后添加以下方法:
func deleteRowAtIndexPath(indexPath: NSIndexPath) { let realm = RLMRealm.defaultRealm() // 1 let objectToDelete = species[UInt(indexPath.row)] as! SpeciesModel // 2 realm.beginWriteTransaction() // 3 realm.deleteObject(objectToDelete) // 4 realm.commitWriteTransaction() // 5 species = SpeciesModel.allObjects().sortedResultsUsingProperty("name", ascending: true) // 6 tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) // 7 }
對應(yīng)的標(biāo)號注釋如下:
首先,我們獲取到默認的 Realm 數(shù)據(jù)庫
然后,我們從數(shù)據(jù)中找到我們想要刪除的對象
啟動寫操作事務(wù)
調(diào)用 deleteObject() 方法,將要刪除的對象傳遞進去, realm 會自動幫我們執(zhí)行刪除操作
接著提交寫操作事務(wù),將刪除操作提交到數(shù)據(jù)庫當(dāng)中
一旦我們移除了一個物種,我們需要重新讀取數(shù)據(jù)
最后,我們更新 UITableViewCell ,將單元格移除
接著,找到 tableView(_:commitEditingStyle: forRowAtIndexPath:) 方法,然后將以下代碼加入到 if 語句塊當(dāng)中:
deleteRowAtIndexPath(indexPath)
當(dāng)表視圖執(zhí)行一個單例刪除操作時,會調(diào)用這個協(xié)議代理,我們所需要做的就是調(diào)用我們剛剛創(chuàng)建的那個方法。
編譯并運行您的應(yīng)用,查看 “ 記錄 ” 界面,然后在某個記錄上面左滑刪除。隨后關(guān)閉模擬器,用 Realm Browser 打開數(shù)據(jù)庫,我們就可以看到我們成功執(zhí)行了更改:
執(zhí)行刪除操作
斷言匹配
我們?nèi)匀贿€想要給這個應(yīng)用提供一些碉堡的功能,那么快速查找怎么樣?在海量的數(shù)據(jù)中進行查找還是很麻煩的一件事情,但是有了快速查找,一切就都簡單了。我們現(xiàn)在所擁有的這個項目已經(jīng)包含了一個 UISearchController 控件,您所需要做的就是添加一點小小的修改,讓這個功能能夠在 Realm 中正常工作。
打開 LogViewController.swift ,然后將 searchResults 屬性替換為以下代碼:
var searchResults: RLMResults!
因為我們?nèi)匀皇菆?zhí)行 “ 檢索 ” 操作,因此我們的數(shù)據(jù)是存放在 RLMResults 當(dāng)中的。
向類中添加以下方法:
func filterResultsWithSearchString(searchString: String) { let predicate = "name BEGINSWITH [c]'\(searchString)'" // 1 let scopeIndex = searchController.searchBar.selectedScopeButtonIndex searchResults = SpeciesModel.objectsWhere(predicate) // 2 switch scopeIndex { case 0: searchResults = searchResults.sortedResultsUsingProperty("name", ascending: true) // 3 case 1: searchResults = searchResults.sortedResultsUsingProperty("distance", ascending: true) // 4 case 2: searchResults = searchResults.sortedResultsUsingProperty("created", ascending: true) 5 default: return } }
對應(yīng)的標(biāo)號注釋如下:
首先我們創(chuàng)建了一個字符串版本的 “ 斷言 (predicate)” ,在這里,我們搜索以 searchString 開頭的 name 屬性。 [c] 可以讓 BEGINSWITH 以不區(qū)分大小寫的靈敏度來進行查找,要注意, searchString 是被單引號括起來的。
我們根據(jù)這個斷言,使用 objectsWhere 這個方法來執(zhí)行斷言檢索操作。
如果選中的標(biāo)簽是 “ 名字 ” ,那么結(jié)果就按照 “ 名字 A-Z” 排列
如果選中的標(biāo)簽是 “ 距離 ” ,那么就按照距離排列結(jié)果
如果選中的標(biāo)簽是 “ 創(chuàng)建時間 ” ,那么就按照時間來進行排列。
因為搜索會導(dǎo)致表視圖調(diào)用同樣的數(shù)據(jù)源方法,因此我們需要對 tableView(_:cellForRowAtIndexPath:) 進行小小的修改,以便讓其能夠處理主要的表視圖記錄以及查詢結(jié)果。在這個方法里面,找到以下代碼:
speciesModel = species[UInt(indexPath.row)] as! SpeciesModel將其替換為以下代碼:
if searchController.active { speciesModel = searchResults[UInt(indexPath.row)] as! SpeciesModel }else { speciesModel = species[UInt(indexPath.row)] as! SpeciesModel }
上面這行代碼將會檢查 searchController 是否激活。如果激活的話,那么就接收并顯示搜索結(jié)果的數(shù)據(jù);如果不是的話,那么就接收并顯示 species 全部數(shù)據(jù)。
最后,我們需要一個功能,那就是單擊范圍欄上的按鈕時,更變返回結(jié)果的排列順序。
將空 scopeChanged 方法用以下代碼來替換:
@IBAction func scopeChanged(sender: UISegmentedControl) { switch sender.selectedSegmentIndex { case 0: species = SpeciesModel.allObjects().sortedResultsUsingProperty("name", ascending: true) case 1: break case 2: species = SpeciesModel.allObjects().sortedResultsUsingProperty("created", ascending: true) default: species = SpeciesModel.allObjects().sortedResultsUsingProperty("name", ascending: true) } tableView.reloadData() }
在上面的代碼中,我們將會檢查范圍欄上的按鈕是哪一個被按下( A-Z ,距離,以及添加日期),然后調(diào)用 sortedResultsUsingProperty 來進行排序。通常情況下,這個列表將按照名字來排序。
您可能會注意到,現(xiàn)在按照距離排序這一塊中目前還是空的( case 1 ),那是因為目前數(shù)據(jù)模型中還不存在 “ 距離 ” 這么一個玩意兒,因此我們暫時還不需要做這個工作,等到以后添加了再來完善。不過現(xiàn)在,我們已經(jīng)可以看到它的大致功能了!
編譯并運行您的應(yīng)用,嘗試一些搜索操作,然后查看結(jié)果!
查看不同的結(jié)果
提示:在作者的原教程中,搜索功能實際上是無法實現(xiàn)的。如果您在 “ 合適 ” 的地方添加了相關(guān)方法,那么實際上程序仍然還是無法執(zhí)行搜索功能的。它會提示 cell 的 titleLabel 的值為 nil 。因為在原教程中, Cell 是在 Storyboard 里面自定義的,而搜索欄則是要顯示一個新的表視圖。如果需要重用自定義的 Cell ,那么最好需要在 Xib 文件中進行制作。因為如果沒有 init(style:reuseIdentifier:) 方法的 Cell 自定義類,是無法進行重用的。
更新記錄
我們現(xiàn)在已經(jīng)實現(xiàn)了添加和刪除記錄的功能了,剩下就是更新數(shù)據(jù)功能了。
如果您試著單擊 LogViewController 中的一個單元格,那么就會跳轉(zhuǎn)到 AddNewEntryViewController 頁面,但是這些區(qū)域都是空白的。當(dāng)然,我們首先要做的就是讓這個頁面顯示數(shù)據(jù)庫中存放的數(shù)據(jù),以便讓用戶編輯。
打開 AddNewEntryViewController.swift 文件,然后向類中添加以下方法:
func fillTextFields() { nameTextField.text = species.name categoryTextField.text = species.category.name descriptionTextView.text = species.speciesDescription selectedCategory = species.category }
這個方法將會使用 species 中的數(shù)據(jù)來填充用戶界面的文本框。記住, AddNewEntryViewController 只有在添加新物種時才會保持文本框為空的狀態(tài)。
接下來,向 viewDidLoad() 方法的末尾添加以下語句:
if species == nil { title = " 添加新的物種 " }else { title = " 編輯 \(species.name)" fillTextFields() }
上面這些代碼段設(shè)置了導(dǎo)航欄的標(biāo)題,以通知用戶當(dāng)前其是在添加新的物種還是在更新一個已存在的物種信息。如果 species 不為空,那么就調(diào)用 fillTextFields 方法來填充文本框。
現(xiàn)在我們需要一個更新功能,以便響應(yīng)用戶的更改操作。向類中添加以下方法:
func updateSpecies() { let realm = RLMRealm.defaultRealm() realm.beginWriteTransaction() species.name = nameTextField.text species.category = selectedCategory species.speciesDescription = descriptionTextView.text realm.commitWriteTransaction() }
通常情況下,這種方法一般都先獲得默認的 Realm 數(shù)據(jù)庫,然后將數(shù)據(jù)寫入的操作放在 beginWriteTransaction() 和 commitWriteTransaction() 方法之間。在這個事務(wù)中,我們只是簡單的更新了這三個數(shù)據(jù)域的值。
這六行短短的代碼就足以完成 Species 記錄的更新操作了哦 ~O(∩_∩)O~
現(xiàn)在我們只需要在用戶單擊保存按鈕的時候調(diào)用上述代碼即可。找到 shouldPerformSegueWithIdentifier(_:sender:) ,然后在 return true 語句之前,第一個 if 代碼塊之內(nèi)添加以下代碼:
else { updateSpecies() }
當(dāng)恰當(dāng)?shù)臅r候,就會調(diào)用這個方法來對數(shù)據(jù)進行更新。
現(xiàn)在打開 LogViewController.swift ,然后將 prepareForSegue(_:sender:) 用以下代碼替換:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "Edit" { let controller = segue.destinationViewController as! AddNewEntryController var selectedSpecies: SpeciesModel! let indexPath = tableView.indexPathForSelectedRow() if searchController.active { let searchResultsController = searchController.searchResultsController as! UITableViewController let indexPathSearch = searchResultsController.tableView.indexPathForSelectedRow() selectedSpecies = searchResults[UInt(indexPathSearch!.row)] as! SpeciesModel }else{ selectedSpecies = species[UInt(indexPath!.row)] as! SpeciesModel } controller.species = selectedSpecies } }
我們在這里將選中的物種信息傳遞給了 AddNewEntryController 。上面的 if/else 代碼是因為要根據(jù)用戶是否是在查看搜索結(jié)果來決定的。
編譯并運行您的應(yīng)用,打開記錄視圖,并且選中一個存在的物種。您應(yīng)該可以看到文本框中已經(jīng)填充了數(shù)據(jù)。
更新數(shù)據(jù)
把剩下的東西結(jié)束掉
要讓我們的應(yīng)用變得更加完美,我們就需要實現(xiàn)剩下 的功能。
還記不記得我們沒有變法根據(jù)距離來排序記錄?我們需要在里面添加不少的代碼,才能夠正常的運行這個功能,但是這個結(jié)果是非常值得的。
打開 Species.swift 文件,然后向類中添加一個新的屬性。
dynamic var distance: Double = 0
這個屬性為保存用戶位置和該記錄點的距離信息。然而,沒有必要去存儲 distance 信息,因為用戶位置會隨時發(fā)生改變。我們想讓距離成為這個模型的一部分,但是我們并不想 Realm 來存儲這個數(shù)據(jù)。
Realm 支持一種被稱為忽視屬性 (ignored properties) 的東西,然后向類中添加以下代碼:
func ignoredProperties() -> NSArray { let propertiesToIgnore = [distance] return propertiesToIgnore }
要實現(xiàn)忽視屬性,只需要聲明一個命名為 ignoredProperties() 的方法,然后返回一個屬性數(shù)組,里面保存有您不想進行存儲的屬性。
由于我們并不會存儲距離這個屬性,很明顯地我們需要自己計算距離。
打開 MapViewController.swift ,添加以下方法:
func updateLocationDistance() { let realm = RLMRealm.defaultRealm() if results != nil { for result in results! { let currentSpecies = result as! SpeciesModel let currentLocation = CLLocation(latitude: currentSpecies.latitude, longitude: currentSpecies.longitude) let distance = currentLocation.distanceFromLocation(mapView.userLocation.location) realm.beginWriteTransaction() currentSpecies.distance = Double(distance) realm.commitWriteTransaction() } } }
對于每個物種,我們計算了這個標(biāo)記點與用戶當(dāng)前位置之間的距離。即時我們沒有存儲這個距離信息,我們?nèi)匀恍枰獙⑵浯鎯υ谟涗洰?dāng)中,然后將其在寫操作事務(wù)中保存這個變化消息。
接下來,在 prepareForSegue(_:sender:) 方法底部添加以下代碼:
else if segue.identifier == "Log" { updateLocationDistance() }
現(xiàn)在,在用戶打開 “ 記錄界面 ” 之前,我們需要調(diào)用這個方法來計算距離。
接下來,打開 LogViewController.swift ,然后找到 tableView(_:cellForRowAtIndexPath:) 方法。然后在這個方法底部附近, return 語句之前添加以下代碼:
if speciesModel.distance < 0 { cell.distanceLabel.text = "N/A" }else { cell.distanceLabel.text = String(format: "%.2fkm", speciesModel.distance / 1000) }最后,找到 scopeChanged() 然后將 case 1 中的 break 替換成以下代碼:
species = SpeciesModel.allObjects().sortedResultsUsingProperty("distance", ascending: true)編譯并運行應(yīng)用,然后 …… 呃?怎么崩潰掉了?
'RLMException`, reason: 'Column count does not match interface - migration required'
什么鬼?
當(dāng)我們向 Species 模型中添加了一個新的 distance 屬性的時候,我們就對架構(gòu)( schema ) 進行了變更,但是我們并沒有告訴 Realm 如何處理這個新增的數(shù)據(jù)段。從舊版本的數(shù)據(jù)庫遷移( migrate ) 到新版本的數(shù)據(jù)庫的操作超出了本教程的范圍。這并不是 Realm 獨有的問題, Core Data 同樣也需要在添加、變更或者刪除新的數(shù)據(jù)段的時候進行遷移操作。
本教程的簡單解決方案就是將模擬器的應(yīng)用移除掉即可,然后重新編譯并運行應(yīng)用程序。這將會讓應(yīng)用創(chuàng)建一個全新的數(shù)據(jù)庫,使用新的架構(gòu)。
從模擬器刪除這個應(yīng)用,接下來編譯和運行這個應(yīng)用。然后添加新的物種,接著打開這個記錄視圖,這時候我們就可以看到如下所示的距離信息:
距離信息
您或許需要模擬一個位置以便能夠計算當(dāng)前距離,在模擬器菜單欄上,選擇 Debug\Location ,然后選擇列表中的一個位置模擬。
接下來該何去何從?
在本教程中,我們學(xué)習(xí)了如何創(chuàng)建、更新、刪除以及查找 Realm 數(shù)據(jù)庫中的數(shù)據(jù)記錄,以及如何使用斷言來進行查找,還有按屬性名對結(jié)果進行排序的方法。
您可能要問了: “ 看起來 Realm 似乎是一個新項目,我感覺在一個完備的應(yīng)用中使用它可能并不穩(wěn)定 ” 。
Realm 最近才想公眾開放,但是早在 2012 年它就已經(jīng)在公司級別的產(chǎn)品中使用了。我個人已經(jīng)在我的既有項目中使用 Realm 了,而且似乎運轉(zhuǎn)起來相當(dāng)不錯。
如果您使用 Objective-C ,那么 Realm 是十分穩(wěn)定的。對于 Swift 來說,由于 Swift 版本并不穩(wěn)定,因此在使用 Realm 可能會遭遇到版本更迭所引發(fā)的一系列語法問題。不過隨著 Swift 的更新,相信 Swift 的版本改動會越來越少, Realm 在 Swift 上也會越來越穩(wěn)定。
對于 Realm 來說,它還有許多在本教程沒有介紹到的特點:
遷移( Migrations ) :在本教程中,我們看到了對 Realm 架構(gòu)的修改導(dǎo)致了錯誤的產(chǎn)生。要學(xué)習(xí)關(guān)于如何在多版本之間遷移數(shù)據(jù)庫的只是,請查看 Realm 說明文檔的 “ 遷移( migrations) ” 部分。
其他類型的 Realm 數(shù)據(jù)庫 :在本教程中,我們一直都是使用著 “ 默認 ”Realm 數(shù)據(jù)庫,但是我們?nèi)匀贿€可以使用其他類型的 Realm 數(shù)據(jù)庫,比如說不存儲數(shù)據(jù)的 “ 內(nèi)存數(shù)據(jù)庫( in-memory realm ) ” 。我們也可以使用多個 Realm 數(shù)據(jù)庫,如果我們享用多個數(shù)據(jù)庫來保存多種類型的數(shù)據(jù)的話。
關(guān)于 Realm 的更多信息,您可以查看 官方文檔 ,我發(fā)現(xiàn)這個文檔真的寫得十分詳盡。
如果您對本教程有什么建議和意見,請到評論區(qū)進行討論,我會盡快處理這些建議和意見的 ~
原文作者 Bill Kastanakis
譯者及改編 星夜暮晨(QQ:412027805)