原文出處:HOSSAM GHAREEB譯文出處:Prayer’s blog(@EclipsePrayer)
智能手機的快速發展的同時,湧現出了很多對開發者友好的開發工具,這些工具不僅使得開發變得更加容易,同時也保證了性能和產品質量。如今,想要在 App Store 占據一席之地,並非易事。想要使得應用易於擴展就更加困難了。當你成功獲得百萬量級的用戶時,應用中的每一個細節都不能放過,並且需要在很短的時間完成對細節的打磨。和數據庫打交道,是很多開發者都會面臨的一個問題。相信我們每個人都會因為數據庫引起的各種問題而頭疼不已,對於數據庫,我想如今我們只有兩種選擇:SQLite和Core Data。我是 Core Data 的忠實粉絲,它對記錄(records)的處理和持久化數據的能力非常強大,但是我意識到,在開發應用的過程中,我在 Core Data 上浪費了太多的時間。最近,我無意中發現了 Realm,一個可以替代 SQLite 和 Core Data 的更好的解決方案。
Realm 是一個跨平台的移動終端數據庫,支持 iOS(Swift 和 Objective-C 語言都支持)和 Android。Realm 的目的就是提供比 SQLite 和 Core Data 更好更快的數據庫支持。它不僅僅是更好和更快,而且更加易於使用,短短幾行代碼就可以完成很多操作。Realm 完全免費,你可以隨意使用它。Realm 是為移動設備而生的,因為在過去的十年中,移動終端的數據庫技術沒有任何的革新。現在如果和移動終端的數據庫打交道,你只有一種選擇,使用 SQLite 或者是底層封裝了 SQLite 的技術比如 Core Data。Realm 的目的是更加易用,它並不是一個建立在 SQLite 之上的 ORM,而是一個基於自己的持久化引擎,簡單並且快速的面向對象移動數據庫。
Realm擁有令人難以置信的速度並且使用起來非常簡單,你會發現,無論是想完成數據庫的讀還是寫操作,都只需要短短的幾行代碼。下面我會列出它的所有優勢,並說明為什麼 Realm 是你在移動應用上數據庫的不二選擇:
讓我們開始學習 Realm,使用它來構建一個使用 Swift 語言的簡單的 iPhone 應用。所完成的 demo 程序是一個簡單的 Todo 應用。用戶可以增加任務清單,每個任務清單都能夠包含多個任務。任務擁有名稱,備注,到期日期,可以添加圖片,並且擁有一個布爾值來表示該任務是否已經完成。在開始創建 Xcode project 之前,我們需要先配置好 Xcode,安裝使用 Realm 所需的工具。
請注意需要滿足下列要求
在開始配置 Xcode 項目之前,請確保運行環境已經正確安裝了 CocoaPods,我們將使用它來為項目安裝 Realm。如果對CocoaPods不熟悉,你可以查看在線的教程,這些教程的材料足夠讓你明白如何開始使用它。
現在,使用Single View Application
項目模板創建一個 Xcode 工程,命名為RealmTasks
或者其他你喜歡的名字。請確保選擇 Swift 作為開發語言。之後使用終端進入該工程目錄,使用如下命令來初始化 CocoaPods:
然後使用 Xcode 打開生成的 Podfile 文件,在 target 之後,添加pod 'RealmSwift'
,修改完之後,應該是下面這個樣子:
下面運行pod install
命令來把 Realm 下載到項目中。完成之後,你會在你的工程文件目錄發現一個新生成的 Xcode 工作空間。請確保打開RealmTasks.xcworkspace而不是 xcodeproj。打開工作空間後,你會得到下面的情形:
現在就可以在 Xcode 中使用 Realm 啦,但是我們將安裝下面的小工具來讓使用 Realm 的過程更加容易。
Realm 開發團隊提供了一個生成 Realm 模型非常有用的工具。為了安裝這個插件,我們要使用Alcatraz。如果你不知道 Alcatraz 是什麼的話,這裡解釋一下,它是一個簡單有用的開源包管理工具,它可以讓你無需任何配置,自動地為 Xcode 安裝插件,文件模板和顏色主題。為了安裝 Alcatraz,你只需將以下代碼復制到終端中執行,之後重啟 Xcode:
Objective-C 1 curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/master/Scripts/install.sh | sh然後在 Xcode 中,選擇Window
->Package Manager
,如下:
然後在彈出的窗口中,你可以選擇安裝你喜歡的插件或者文件模板,在搜索框中你可以搜索你喜歡的插件、配色。在搜索框輸入“Realm”,在出現的結果中選擇“RealmPlugin”,點擊INSTALL
按鈕
給大家介紹的最後一個工具是 Realm 浏覽器。這個浏覽器可以幫助你閱讀和編輯你的.realm
數據庫文件。這些文件在應用程序中被創建,在數據庫表中保存了所有的數據實體(entities),屬性(attributes)和記錄(records)。之前我們說過,這些數據庫文件可以在不同的平台像 iOS 和 Android 平台間共享。想要下載 最新版本的 Realm 浏覽器,請訪問iTunes store。打開 Realm 浏覽器,選擇Tools
->Generate demo database
。它會幫你生成 realm 數據庫測試文件,你可以使用該浏覽器打開和編輯它的內容。當你打開的時候,你會看到像下面的內容:
在 RealmTestClass1 中,它擁有 1000 條記錄,顯示了多種不同的參數類型。我們會在下節介紹支持的屬性類型。
現在,Realm 的所有准備工作都已就緒,那我們開始動手把。
好戲才剛剛開始。首先我們來創建模型類或者說我們的數據庫。為了創建 Realm 數據模型類,你只需要簡單地新建一個普通的 Swift 類繼承自 Object 就可以了。因為 Realm 數據模型類的基類是 Object,所以 Object 的子類都可以擴展為 Realm 的模型類。在創建類之後,就可以添加屬性了。Realm 支持以下多種數據類型:
Realm 中的 List 可以包含多個 Object 實例,參考上面 demo 數據庫的截圖,最後一列表示在其他數據表中的存在的一組引用。在和 Realm 模型類打交道的時候,使用的方式和其他 Swift 類一樣。例如,你可以添加方法或者遵循指定的協議。
多說無益,來看代碼
現在讓我們使用之前在 Xcode 中安裝的 Realm 插件來新建一個 Realm 類。打開 Xcode,新建文件,選擇 Realm:
然後選擇 Swift 語言,類名我們輸入 Task。會得到如下結果:
現在,可以向 Task 數據模型中添加屬性了。
我們需要在數據模型中添加需要的屬性。該例子中,Task 需要有 name (String), createdAt (NSDate), notes (String), 和 isCompleted (Bool)這些屬性。添加這些之後,代碼應該像下面醬紫:
Objective-C 1 2 3 4 5 6 7 8 9 10 11 12 13 class Task: Object { dynamic var name = "" dynamic var createdAt = NSDate() dynamic var notes = "" dynamic var isCompleted = false // Specify properties to ignore (Realm won't persist these) //override static func ignoredProperties() -> [String] { //return [] //} }我們已經為 Task 添加了屬性,所有的屬性前面都加了dynamic var
,這使得屬性可以被數據庫讀寫。
接下來,我們要創建 TaskList 模型類,用來存儲 Task 實例:
Objective-C 1 2 3 4 5 6 7 8 9 10 11 12 class TaskList: Object { dynamic var name = "" dynamic var createdAt = NSDate() let tasks = List<Task>() // Specify properties to ignore (Realm won't persist these) //override static func ignoredProperties() -> [String] { //return [] //} }TaskList 模型類擁有 name,createAt 和一個包含 Task 的 List 屬性。需要注意的是:
List 用來表示一對多的關系:一個 TaskList 中擁有多個 Task List 和 Array 在使用上非常相似,所用的方法和訪問數據的方式(索引和下標)都相同。List 後標明了數據類型,所包含的所有對象都應該是相同類型的。 List 是泛型,這也是為什麼我們沒有在聲明前面加上 dynamic 的原因,因為在 Objective-C 運行時無法表示泛型屬性。
在 Realm 中創建數據關系非常直觀,就像在之前實現中你看到的一對多關系那樣。在使用一對一的關系時,我們將使用 Object 類型,來看下面的例子:
Objective-C 1 2 3 4 5 6 7 class Person: Object{ dynamic var name = "" } class Car: Object{ dynamic var owner:Person? }上面的例子中,owner 屬性表示 Car 和 Person 之間的一對一數據關系。
現在基本的數據模型都已經創建好了。接下來我們會在做一個 ToDo 應用的同時,來討論 Realm。首先,從這裡下載 app,在 Xcode 7 或更高的版本中運行,就像下面這樣:
在項目中,我添加了兩個視圖控制器: TasksViewController 和 TaskListViewController。第一個視圖控制器用來顯示單個 task,第二個視圖控制器用來顯示所有的 TaskList。在 list 視圖中,點擊 + 按鈕來添加一個任務清單。選擇一個任務清單將會詳情視圖。你可以在這裡添加多個 task。
了解了 demo 的大體思路之後,現在讓我們來看看如何向 Realm 數據庫中添加一個新的任務清單。為了實現這個功能,需要如下處理:
創建 TaskList 實例對象,並將其保存到 Realm 數據庫中 向數據庫中查詢 list 數據,來更新 UI
為了在 Realm 中保存數據,你只需要實例化繼承自 Object 的數據模型類,然後將對象寫入到 Realm 中,下面是示例代碼:
Objective-C 1 2 3 4 5 6 7 8 9 10 11 let taskListA = TaskList() taskListA.name = "Wishlist" let wish1 = Task() wish1.name = "iPhone6s" wish1.notes = "64 GB, Gold" let wish2 = Task(value: ["name": "Game Console", "notes": "Playstation 4, 1 TB"]) let wish3 = Task(value: ["Car", NSDate(), "Auto R8", false]) taskListA.tasks.appendContentsOf([wish1, wish2, wish3])通過實例化 TaskList 類,我們創建了一個任務清單,之後設置了它的屬性。隨後我們創建了 3 個 Task 類型的對象。這裡我們演示了創建 Realm 對象的三種途徑:
wish1 的實例化方式:簡單的實例化 Realm 類,然後設置屬性 wish2 的實例化方式:傳入一個字典,字典中的 key 為屬性名,值為要設置的值 wish3 的實例化方式:使用數組傳入的方式。數組中值的順序需要和模型類中的聲明順序一致。
在 Realm 中還可以是使用嵌套的方式來創建對象。在一對一關系和一對多關系的時候,你可以使用這種方式,這時候,一個類型對象的初始化需要一個或多個另一個類型的對象。面臨這種情況的時候,你可以選擇上面的第二或者第三種方法,使用一個字典或者一個數組來表示一個對象:
Objective-C 1 let taskListB = TaskList(value: ["MoviesList", NSDate(), [["The Martian", NSDate(), "", false], ["The Maze Runner", NSDate(), "", true]]])在上面的代碼中,我們創建了一個電影清單,並設置了清單名稱、創建時間和清單內容,清單內容包括多個 task。每個 task 使用數組的方式來創建,例如[“The Maze Runner”, NSDate(), “”, true]
表示一個task,內容分別對應了名稱,創建時間,備注和是否已經完成。
現在你應該知道了如何在 Realm 中創建和使用對象,但是為了能夠在應用程序重新啟動的時候使用這些數據,需要使用寫事務將它們持久化到 Realm 的數據庫中。當使用 Realm 來持久化數據的時候,只要這些對象已經存儲成功,你可以在任何線程中獲取這些對象。一個 Realm 實例表示一個 Realm 數據庫。可以像下面一樣實例化它:
Objective-C 1 let uiRealm = try! Realm()我們常常將上面這行代碼寫在AppDelegate.swift
文件的頂端(譯者注:類之外,全局變量),這樣就可以在整個項目中獲得該對象的引用。之後便可以很方便地調用它的讀和寫方法:
首先,uiRealm 對象已經在 AppDelegate 中創建,在整個 app 中都可以使用。Realm 對象在每個線程中都應該只被創建一次,因為它不是線程安全的,不能在不同的線程中共享。如果你想要在另一個線程中執行寫操作,那麼就需要創建一個新的 Realm 對象。我將這個實例命名為uiRealm
,就是因為它應該只在 UI 線程中被使用。
現在讓我們回到我們的 app 中,我們需要在用戶點擊 Create 按鈕的時候保存任務列表。在TasksViewController
的displayAlertToAddTask
方法中,我們有一個createAction
對象:
在上面的代碼中,我們從 TextField 中獲取到任務名稱,調用 Realm 的鞋方法來保存任務列表。
請注意,當你同時進行多個寫操作的時候,他們會相互阻塞,阻塞住他們所運行的線程。所以應當考慮在 UI 之外的線程中來進行操作,另外需要注意的是,在進行寫事務的時候,讀操作並不會造成阻塞。這非常有用,尤其是當你在後台進行寫操作的時候,用戶可能會在不同界面切換,而這時候可以進行讀操作。
現在你已經學會了如何在 Realm 中寫數據,下面我們來看看如何檢索數據。在 Realm 中檢索數據的方式非常直觀。Realm 提供了很多選項來過濾出你想要的數據。在 Realm 中進行查找操作的時候,它將會返回一個 Results 對象。可以把 Results 簡單地當做是 Swift 的數組,因為它們的接口非常類似。
當得到 Results 實例的時候,已經從磁盤中直接獲取到了數據。對這些數據的任何操作(使用事務)將會影響到磁盤上的數據。在 Realm 中來檢索數據,只需要調用對象的方法,並將類名作為參數傳進去。讓我們看看如何使用這種方式來讀取 TaskLists 並更新 UI:
我們在TasksListsViewController
中定義了該屬性:
然後實現了readTasksAndUpdateUI
方法:
在tableView(_:cellForRowAtIndexPath:_)
方法中,我們將顯示列表的名稱,還有每個列表內的任務個數:
代碼邏輯非常直觀吧。最後需要做的就是在viewWillAppear
中調用readTasksAndUpdateUI
方法,來確保總是顯示數據更新之後的視圖。
上面展示了如何使用 Realm 來進行任務列表的讀寫操作。接下來,我們來看看如何進行數據更新和刪除操作。在開始之前,先來看看項目中的修改和刪除不分的相關代碼。
首先在TaskListsViewController
中,我們用一個布爾值isEditingMode
來表示是在正常狀態還是編輯狀態:
當 Edit 按鈕被點擊的時候,將會調用didClickOnEditButton
方法:
這個方法會使用 table view 的setEditing
方法來開啟或禁用 UITableView 的編輯模式。在表格視圖中,默認的編輯操作是刪除,但是從 iOS 8.0 開始,增加了一個editActionsForRowAtIndexPath
方法來自定義一些操作,這些操作在在用戶滑動表格 cell 的時候出現。
我們將使用該方法來添加兩個功能,刪除和編輯,代碼如下:
Objective-C 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? { let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Destructive, title: "Delete") { (deleteAction, indexPath) -> Void in //Deletion will go here let listToBeDeleted = self.lists[indexPath.row] uiRealm.write({ () -> Void in uiRealm.delete(listToBeDeleted) self.readTasksAndUpdateUI() }) } let editAction = UITableViewRowAction(style: UITableViewRowActionStyle.Normal, title: "Edit") { (editAction, indexPath) -> Void in // Editing will go here let listToBeUpdated = self.lists[indexPath.row] self.displayAlertToAddTaskList(listToBeUpdated) } return [deleteAction, editAction] }這裡我們使用UITableViewRowAction
添加了兩個操作,方法中定義了操作的style
,title
和handler
。當在滑動 cell 或者以其他方式進入編輯模式的時候,會像下面這樣:
以上就是在進行刪除和更新操作時候的 UI 代碼邏輯。
想要從 Realm 數據庫中刪除對象或者數據,你只需要調用 Realm 對象的delete
方法,同時將該對象作為參數傳入。當然,這些操作會在寫事務中完成。來看一下下面的代碼的工作方式,我們從 Realm 數據庫中刪除了一個任務列表:
在刪除之後,我們調用了readTasksAndUpdateUI
方法來讀取數據並更新 UI。
在 Realm 中,還有一個方法叫做deleteAll
,它允許你刪除數據庫中所有 class 的所有數據。如果你想為當前用戶持久化數據,但是在他退出登錄的時候抹掉所有的相關數據,這個方法將會十分有用。
在 Realm 中有多種方式可以來更新 object,但是這些方法都應該在寫事務中完成。下面我們來看一些更新對象的方式。
使用 properties
你可以通過直接在 Realm 的寫閉包中設置 property 的值來更新數據。例如,在TasksViewController
中,我們可以簡單地設置屬性值來更新任務的狀態信息:
使用主鍵
Realm 支持將某個 string 或 int 類型的屬性設置為主鍵。當使用add()
方法來創建 Realm 對象時,如果有相同主鍵的對象存在,就會更新這個對象的值。下面是示例代碼:
這裡的 id 屬性為主鍵。如果 id 為 1 的用戶存在,Realm 會更新相應的對象。如果不存在,Realm 將會把該對象存入數據庫中。
使用 KVC (Key-Value Coding)
如果你是 iOS 開發的老手,那麼對 Key-Value Coding 肯定不會陌生。Realm 的類型,像 Object,Results 和 List,都可以使用 Key-Value Coding。該特性可以幫助你在運行時設置或更新屬性值。另外一個支持 KVC 的好處是,可以在無需遍歷每個對象的情況下,批量更新對象數據。這麼說你可能不是很理解,我們來看個例子:
Objective-C 1 2 3 4 let tasks = uiRealm.objects(Task) uiRealm.write { () -> Void in tasks.setValue(true, forKeyPath: "isCompleted") }在上面的代碼中,使用查詢語句來請求所有的 Task 對象,之後將所有得到的對象的isCompleted
屬性設置為 true。可以看出,將 Realm 中的所有 tasks 標記為已完成,僅僅只用了一行代碼。
讓我們回過頭來看看我們的 ToDo 應用。如果仔細觀察displayAlertToAddTaskList
方法,你會看到如下代碼片段:
當用戶遍歷列表名稱的時候,上面的代碼會被調用。通過設置 name 屬性的方式,就更新了數據庫中的內容。
我們將一起看看TaskListViewController
的大部分代碼。現在讓我們打開TasksViewController
來看看,這個視圖控制器用來顯示任務清單中的任務項。視圖控制器擁有一個 UITableView, 該視圖有兩個 section:完成的任務和未完成的任務。在TasksViewController
中,有如下屬性:
selectedList
用來存儲TaskListsViewController
傳遞過來的選中的任務列表。為了將完成和未完成的任務分開,聲明了兩個屬性,openTasks
和completedTasks
。為了過濾出不同的任務完成狀態,我們將使用 Realm 的方法filter()
。在解釋該函數如何工作之前,讓我們先來看看如何在代碼中使用它:
在上面的方法中,我們使用filter
方法來過濾results
。Realm 提供了filter()
方法來過濾數據。該方法可以被 List、Result 和 Object 對象調用。方法會返回過滿足濾條件參數的特定對象。你可以把filter
當做NSPredicate
。基本上來說,你可以認為這兩者差不多。就像上面的代碼一樣,你同樣可以使用 string 作為參數創建一個NSPredicate
對象,並把它作為參數傳給filter
方法。
讓我們來看另外一個例子:
Objective-C 1 2 3 4 5 6 // using predicate string var redCars = realm.objects(Car).filter("color = 'red' AND name BEGINSWITH 'BMW'") // using NSPredicate let aPredicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "red", "BMW") redCars = realm.objects(Car).filter(aPredicate)在上面的代碼中,我們使用filter
方法來過濾color
為 red,並且name
以 “BMW” 開頭的對象。第一行代碼使用 string 作為參數來進行過濾。另外,你也可以使用 NSPredicate 獲得同樣的效果。下面的表格總結了 filter 方法的大部分常用操作:
既然我們談到了 Realm 數據庫的基本操作,在本教程結束之前,我還想給大家介紹另外一個特性。排序功能,這是 Realm 提供了另一個特別有用的特性。對於 List 和 Result 對象,你可以調用方法sorted(“排序標准”)
來將一組數據進行排序。讓我們來看看如何在任務列表中使用該方法讓任務列表以字母表或者創建時間先後順序排序。首先,在 UI 中,我們增加了一個 segment control,將會根據選擇的情況來進行對應的排序。
根據不同的選擇來進行排序,代碼邏輯如下:
Objective-C 1 2 3 4 5 6 7 8 9 10 11 12 @IBAction func didSelectSortCriteria(sender: UISegmentedControl) { if sender.selectedSegmentIndex == 0{ // A-Z self.lists = self.lists.sorted("name") } else{ // date self.lists = self.lists.sorted("createdAt", ascending:false) } self.taskListsTableView.reloadData() }Realm 是一個非常簡單易用,直觀的本地數據庫解決方案。Realm 提供了很好的可擴展性,只用很少的幾行代碼就可完成操作。對於大部分的應用甚至是游戲來說,我覺得如果需要使用數據庫的話,Realm 值得嘗試。
學習了本教程,你應該可以在項目中使用 Realm 來進行增刪改查等基本的操作。Realm 還具有一些高階特性,值得深入學習。最好的學習資料就是 Realm 網站的官方文檔,Realm 的小伙伴把文檔寫得非常贊!
如果想要教程 ToDo 應用的的完整代碼,你可以在這裡下載
如果對代碼有任何的問題,都歡迎留言反饋,我們會很樂意幫助到您。QQ技術交流群290551701