使用Realm和Swift編寫一個ToDo應用

使用Realm和Swift編寫一個ToDo應用git

做者:HOSSAM GHAREEB,時間:2015/11/28
翻譯:BigNerdCoding, 若有錯誤歡迎指出。原文連接github

在去年智能手機的大更新以後,不少工具也同時被開發出來了。這些工具讓咱們開發者發佈一個高性能、高質量的應用的變的更加簡單了。在應用商店得到高排名以及再也不很容易。並且讓應用更容易拓展也是很困難的一件事。當你的應用成功的擁有百萬級別的用戶的時候,你須要注意應用中的全部事情以及全部操做。所以,如今每一個開發者都須要面臨的一個問題就是處理數據庫。而這又是一件讓人感到很是頭疼的事情,大多數的開發者會在SQLiteCore Data中挑選一個。曾經我是Core Data的擁躉,由於它在處理數據以及持久化數據方面功能很是強大。可是後來我發現使用Core Data會浪費不少時間。如今我會使用Realm,該框架可以很好的替換SQLiteCore Data算法

Realm是什麼

Realm是一個跨平臺的手機端數據庫支持iOS(Swift和Object雙語言版本)、安卓。相比於SQLiteCore Data更好也更快。除此以外,它的使用也很方便之須要幾行代碼就能夠搞定。Realm是一個開源產品你能夠免費試用。Realm之因此會出現是由於在過去的十年中移動數據庫沒有任何可喜的更新。過去在處理移動數據庫的時候,你幾乎只能選擇SQLite或者在其基礎上進行封裝的Core Data。由於Realm並非一個ORM(對象關係映射)且有本身的持久化引擎使得Realm容易使用而且擁有很好的性能和速度。數據庫

爲何選擇Realm

Realm快的難以置信而且易用,你能看見任何你須要的東西,而且只須要幾行代碼就能夠完成數據庫的讀寫操做。這裏我會列出全部在移動端使用Realm的好處和理由:swift

  • 安裝簡單:在後面你會發現安裝Realm比你想象的還要簡單,之須要在Cocoapods中添加簡單的命令就能使用了。數組

  • 速度:Realm遠快過於SQLiteCore Data,官方提供的比較證據xcode

  • 跨平臺:Realm的數據庫文件是跨平臺的,它能夠在iOS和Android中進行分享。不管你是使用Java、Object-C、仍是Swift,你均可以使用高級的模型。瀏覽器

  • 可拓展性:當你的手機應用擁有大量的用戶以及數據記錄的時候可拓展就是很重要的一個特徵。拓展性問題從一開始設計和選擇工具的時候就須要進行認真的考慮。Realm在可以高效處理大數據量的同時依然擁有着很是好的拓展性。在應用中引入該框架會讓程序的速度獲得提高。安全

  • 良好的文檔支持:Realm團隊提供了可讀性強、組織良好的的豐富文檔給你們。若是你依舊有問題解決不了的話,能夠在 Twitter、Github、Stackoverflow上去向它們尋求幫助和解答。閉包

  • 可靠:Realm依舊被大量的創業團隊和公司的移動應用使用像:Pinterest、Dubsmash、Hipmunk。

  • 免費:如此強大,並且仍是徹底免費的。

開始幹活

讓咱們Realm使用教程,並用它建立一個Swift語言版本的iPhone簡單Todo應用。用戶在該應用中能夠添加多個任務鏈表,每一個鏈表裏面又會有多個任務。每一個任務都有一個標題、備註、到期時間,一個圖像附件以及一個標記是否完成的標記量。在開始編寫工程以前咱們首先須要配置Xcode並安裝Realm工做所需的一個工具。

須要的條件

下列條件必須知足:

  • iOS 8 or later、OS X 10.9 or later。

  • Xcode 6.3 or later。

  • Realm的有兩個Swift版本,一個是2.0版本另外一個是1.2版本。咱們在教程中使用的是2.0版本。你也能夠選擇使用1.2版本的,可是該版本在將來不會被維護和支持,所以最安全的辦法就是使用2.0版本。

配置Xcode並安裝工具

再開始配置Xcode以前請確保你已經安裝了CocoaPods,咱們須要使用它在Xcode工程中安裝Realm。若是你對CocoaPods不熟悉的話,你能夠去官網操做安裝教程。

如今,咱們建立一個"Single View Application"模版的工程,並將工程命名爲「RealmTasks」或者你喜歡的名稱。請確保使用的是Swift語言。接下來咱們在終端中切換到當前工程的目錄並按照下面步驟初始化工程的CocoaPods。

pod init

使用編輯器生成的文件podfile,並在文件中添加以下內容:

clipboard.png

接下來運行命令"pod install"去下載安裝Realm到你的工程裏面。當安裝完成後,你會發現文件夾下面又一個新的Xcode workspace被建立了。打開RealmTasks.xcworkspace文件,你會看見以下界面:

如今Realm已經可以使用了,可是咱們仍是安裝一些工具類幫助咱們更加容易的使用Realm。

安裝Realm插件

Realm團隊爲Xcode提供了很好的插件,該插件可以建立Realm模型。咱們使用Alcatraz來安裝這個插件。該工具能夠很好的幫助你自動安裝那些開源的插件,模版、顏色主題。對於那些不知道Alcatrza的開發者來講,這能夠節省不少的時間和精力。直接使用下面的命令安裝Alcatrza:

curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/install.sh | sh

接下來在Xcode中選擇Window菜單欄下面的Package Manager,以下圖:

clipboard.png

在彈出的窗口中選擇你須要安裝的類型,並在搜索框中輸入對應的插件、模版或者主題。咱們選擇Plugins,輸入"Realm",在出現的結果裏面選擇"RealmPlugin"並安裝。以下圖:

clipboard.png

此處可能在Xcode7.1以上版本會出現一些問題,解決方法

Realm Browser

最後一個工具是Realm Browser。該瀏覽器能夠幫助你查看或者編輯你的.realm數據庫文件。這些數據文件在你的應用中被建立出來,而且包含了裏面的實體、屬性、以及數據表中的紀錄。這些文件如以前所說的同樣能夠在像iOS、Android這樣不一樣的平臺之間分享。你能夠在iTunes store下載到最新版本的工具。打開該應用選擇Tools -> Genetate demo database,應用會爲你新建一個測試數據庫文件你能夠在瀏覽器中看到全部的紀錄。以下圖:

clipboard.png

正如上圖顯示的,類RealmTestClass1有1000條紀錄以及不一樣類型的參數(列)。咱們會在下面接受它支持的類型。

如今一切準備工做都已經完成了。開始編碼吧。

數據庫Model類

遊戲開始了!首先咱們須要新建一個模型類。能夠經過建立一個繼承與Object的Swift類。考慮到Object是全部Realm model類的基類,你能夠拓展任何拓展自Obeject的Realm model類。當你建立本身的類的時候,理所固然你須要定義屬性。Realm支持下面各類類型的屬性:

  • Int, Int8, Int16, Int32, and Int64

  • Boolean

  • Float

  • String

  • NSDate

  • NSData

  • Class extends Object => Used for One-to-one relations

  • List<Object> => Used for one-to-many relations

List在Realm類中表示對象實例的集合,就像上面演示數據庫截圖表示的那樣。截圖中的最後一列就是一個存在於另外一張表中紀錄指針的數組。在使用Realm模型類的時候,你能夠像對待其餘Swift類同樣對待它。例如,你能夠在類裏面添加函數方法,協議。

Talk is cheap,show me the code ?

咱們使用剛纔安裝的Realm插件建立一個Realm類。在Xcode中新建文件,在左側選擇Realm。如圖:

clipboard.png

選擇Swift語言,類名爲Task。以下圖:

clipboard.png

如今爲該類添加屬性。

屬性

咱們須要在Task類中添加屬性,每個Task都會有名稱、建立日期、備註、是否完成。添加完成以後代碼以下:

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 []
//  }
}

你能夠發現添加的全部屬性都被聲明爲dynamic var,之因此這樣是爲了讓這些屬性可以被底層數據庫數據訪問到。

接下來,咱們定義一個TaskList類,該類存儲多個任務:

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類有名稱、建立時間、任務鏈表。下面是一些說明補充:

  • List<Object>對應一個任務列表有多個任務這種一對多的關係。

  • List於數組的相似,用戶能夠經過下標索引來訪問鏈表中的數據。注意:鏈表中的數據必須是用一個類型。

  • List<T>是一個泛型數據類型,之因此不在該泛型屬性前面添加dynamic聲明是由於泛型屬性沒法經過Objective-C的運行時表示。

Realm中關係的創建就像你前面看到的一對多的實現同樣簡單直接。一個簡單的一對一的例子以下:

class Person: Object{
    dynamic var name = ""
}

class Car: Object{
    dynamic var owner:Person?
}

上面的示例代碼很好的表現了一對一的關係:每一個人都有一個對應的車主。

到目前爲止,咱們已經建好了基礎的model類。接下來咱們繼續建立Todo應用的教程。首先,下載代碼在Xcode7或者更高的版本中運行,你會看見下面截圖同樣的界面:

clipboard.png

在這個工程中,我添加了兩個視圖控制器:TasksViewController和TaskListViewController。前面一個視圖控制器是用來展現一個任務的細節,第二個視圖控制器是用來顯示全部的任務。在列表視圖中你點擊+按鍵添加一個任務列表。選擇一個視圖列表會跳轉到另外一個視圖中添加多個任務。

帶着演示應用的基本概念,如今讓咱們看看如何添加一個任務鏈表到Realm數據庫中。爲了實現這個功能,咱們須要解決下面兩件事:

  • 建立一個新的TaskList model對象並將其保存到Realm.

  • 使用查詢語句從數據庫中讀出數據並更新界面UI。

爲了將對象保存到Realm,你所須要作的就是實例化Obeject子類的model對象並將其寫入到數據庫。下面就是代碼示例:

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])

咱們建立了一個任務鏈表,並使用初始化方法進行了實例化設置了部分屬性。而後咱們建立了三個task類型的對象(wish1, wish2 and wish3)。在這裏我使用了三種方法來建立Realm對象:

  1. 使用Realm類的實例化方法建立wish1並設置屬性

  2. 經過傳遞鍵值類型的字典類型的屬性來建立wish2。

  3. 經過傳遞一個數組來建立wish3。數組中的值與類中聲明的屬性順序同樣。

嵌套對象

Realm中另外一個建立對象的方法就是嵌套對象。該方法在對象關係是一對一或者一對多的時候可使用(意味着你有一個Object類型的屬性或者一個List<Object>類型的屬性)。若是你使用了上面的方法2或者方法3的話,你可使用一個表示屬性的數組或者字典來取代該方法,代碼以下:

let tasklistB = TaskList(value: ["MoviesList",NSDate(), [["The Martian", NSDate(), "", false], ["The Maze Runner", NSDate(), "", true]]])

在上面的代碼中,咱們新建了一個電影鏈表,設置了名稱、時間、任務數組。每個任務又是經過屬性數組建立的。例如[「The Maze Runner」, NSDate(), 「」, true]就表示名稱、時間、備註、是否已經完成了。

持久化Realm中的對象

如今你知道了如何建立並使用Realm對象。可是爲了在應用從新啓動的時候依舊可以使用這些對象,你須要經過Realm數據庫的寫事務來進行對象持久化。一旦對象數據被持久化了並存在於Realm數據庫中,你就能夠在任何線程中訪問這些對象。爲了執行這個寫事務,你須要一個Realm對象。一個Realm的實例就表明一個Realm數據庫。你能夠以下建立該實例:

let uiRealm = try! Realm()

咱們將該實例定義在AppDelegate.swift文件的上面這樣就能夠在全部的文件中使用了。後面你能夠以下簡單的調用寫方法:

uiRealm.write { () -> Void in
            uiRealm.add([taskListA, taskListB])
}

首先uiRealm對象在AppDelegate類中建立好了而且可以在整個應用中使用。每個線程裏面Realm對象只能建立一次,由於Realm對象不是線程安全的而且不能在不一樣的線程中共享。若是你想在別的線程裏面執行寫事務,你須要建立一個新的Realm對象。這裏建立的uiRealm對象就是在UI線程中使用的。

如今咱們回到app中,當用戶點擊建立按鍵的時候咱們須要保存一個task lists。在TasksViewController文件的displayAlterToAddTask方法中,咱們以下建立對象:

let createAction = UIAlertAction(title: doneTitle, style: UIAlertActionStyle.Default) { (action) -> Void in
    
    let taskName = alertController.textFields?.first?.text
    
    if updatedTask != nil{
        // update mode
        uiRealm.write({ () -> Void in
            updatedTask.name = taskName!
            self.readTasksAndUpateUI()
        })
    }
    else{
        
        let newTask = Task()
        newTask.name = taskName!
        
        uiRealm.write({ () -> Void in
            
            self.selectedList.tasks.append(newTask)
            self.readTasksAndUpateUI()
        })
    }

}

在上面的代碼中,咱們從text field中獲取名稱,而後調用Realm的寫方法保存該任務鏈表。

請注意:當有多個線程同時執行寫操做的事務時,它們都會阻塞對方的線程而且也會阻塞本身所在的線程。因此你應該在一個單獨的線程操做裏面執行寫事務而不是在UI線程裏面。另外一件事時:讀操做不會阻塞寫事務的操做。這在用戶瀏覽應用並伴隨不少讀操做的同時進行後臺數據寫事務時頗有幫助的。

檢索對象

你已經知道在Realm中進行數據寫操做了,可是若是你不知道如何檢索處這些數據那麼寫操做也就沒有意義了!其實Realm中查詢很簡單直接。你傳遞一些自定義的查詢條件,而後執行查詢,篩選出來的結果就會展現在你的面前。你能夠將查詢獲得的結果當作時Swift中的數組,由於它們有着類似的接口。

當你實例化一個結果對象以後,很容易就能獲取磁盤中的數據。事務中對數據的任何修改都會直接影響磁盤中的數據。在Realm中你能夠經過調用以類名做爲參數的方法獲得結果。下面咱們看看如何讀出TaskLists並更新UI:

咱們已經在TasksListsViewController定義了屬性:

var lists : Results<TaskList>!

readTasksAndUpdateUI方法的實現:

func readTasksAndUpdateUI(){
    
    lists = uiRealm.objects(TaskList)
    self.taskListsTableView.setEditing(false, animated: true)
    self.taskListsTableView.reloadData()
}

在tableView(_:cellForRowAtIndexPath:_) 方法裏面,咱們顯示任務鏈表名以及任務數:

func tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
    
    let cell = tableView.dequeueReusableCellWithIdentifier("listCell")
    
    let list = lists[indexPath.row]
    
    cell?.textLabel?.text = list.name
    cell?.detailTextLabel?.text = "\(list.tasks.count) Tasks"
    return cell!
}

是否是很簡單?最後咱們須要在viewWillAppear中調用readTasksAndUpdateUI函數,確保每次視圖出現的時候都是最新的。

override func viewWillAppear(animated: Bool) {
    readTasksAndUpdateUI()
}

以上就是使用Realm進行讀寫task lists的內容了。下面,咱們須要知道Realm中如何進行刪除和更新操做。在開始以前,咱們先來看看工程中lists的編輯、刪除操做的代碼。

首先在TaskListsViewController中有一個isEditingMode的布爾變量,用於編輯和正常模式的切換。

var isEditingMode = false

當點擊編輯按鍵的時候,didClickOnEditButton方法將被調用:

@IBAction func didClickOnEditButton(sender: UIBarButtonItem) {
    isEditingMode = !isEditingMode
    self.taskListsTableView.setEditing(isEditingMode, animated: true)
}

該動做使用table view中的setEditing方法來設置是否處於編輯模式。在table view中編輯模式下的默認方法就是刪除改單元,可是在iOS8.0的UITableViewDelegate中引入了一個editActionsForRowAtIndexPath方法,該方法用於當用戶滑動單元格的時候定製本身的方法。

咱們添加刪除、編輯兩個方法,實現以下:

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]
}

咱們經過帶有style、title、handler的UITableViewRowAction添加了兩個action。如今當你向左滑動或者點擊編輯的時候界面會是下面這樣:

clipboard.png

上面是關於UI如何響應刪除和更新動做的。

刪除對象

爲了刪除Ralm數據庫中的對象或者數據,你能夠經過傳遞須要的對象給delete方法。固然,改操做應該在一個寫事務內部。下面的代碼就是刪除操做的代碼實現:

let listToBeDeleted = self.lists[indexPath.row]
uiRealm.write({ () -> Void in
           uiRealm.delete(listToBeDeleted)
           self.readTasksAndUpdateUI()
    })

當刪除對象後調用readTasksAndUpdateUI方法來更新界面。

出了上面的刪除一個對象,還能夠調用Realm中的deleteAll方法來刪除數據庫中索引類和數據。該方法在當前用戶退出應用而你須要清除數據庫中的全部持久化數據的時候很是有用。

uiRealm.write({ () -> Void in
    uiRealm.deleteAll()
})

更新對象

在Realm中有不少方法能夠實現對象的更新操做,可是全部的的這些方法都必須在一個寫事務裏面。下面咱們將會看見其中的一些更新對象方法。

使用屬性

你能夠經過在寫事務的閉包裏對對象的屬性的值進行從新設置來完成Realm對象的更新。例如,在TasksViewController中咱們能夠經過下面的方法來改變一個任務的完成狀態:

uiRealm.write({ () -> Void in
    task.isCompleted = true
})

使用主鍵

Realm支持使用一個字符串或者整型屬性來做爲對象的主鍵。當用戶經過add()方法來建立新的Realm對象的時候,若是對象的鍵值以及存在,那麼對象會被更新爲一個新的值。下面是示例:

let user = User()
user.firstName = "John"
user.lastName = "Smith"
user.email = "example@example.com"
user.id = 1
// Updating User with id = 1
realm.write {
            realm.add(user, update: true)
        }

上面的id屬性是鍵值,若是已經存在一個id = 1的用戶的時候,Realm會相應的更新該對象。不然直接插入。

使用KVC(Key-Value Coding)

若是你是一個有經驗的iOS開發人員,你必定會對KVC很熟悉了。Realm中的Object、Results、List類都兼容KVC。該特性能讓你在運行時設置更新屬性。另外一個很是好的特性是對於List、Results你能夠以集合的方式批量進行更新,而無需迭代每個來進行更新。可能你還不是很明白,看下面的例子:

let tasks = uiRealm.objects(Task)
uiRealm.write { () -> Void in
    tasks.setValue(true, forKeyPath: "isCompleted")
}

在上面的代碼中,我首先查詢獲得了全部任務對象而後將全部結果的isCompleted設置爲了true。這意味着我只使用了一行代碼就完成了對全部任務的完成標記。

咱們再次回到ToDo app,在displayAlertToAddTaskList方法中你應該可以發現以下的代碼片斷:

//update mode
 uiRealm.write({ () -> Void in
           updatedList.name = listName!
           self.readTasksAndUpdateUI()
 })

該代碼會在用戶編輯list名稱的時候執行。咱們經過設置屬性名來完成更新操做。

顯示Tasks

咱們已經看過了TaskListViewController中的絕大部份代碼。如今咱們來看下用於顯示一個任務列表任務的TasksViewController。該視圖控制器有一個UITableView,這個table view分爲了兩個部分:待完成和已完成任務。在TasksViewController中有以下屬性:

var selectedList : TaskList!
var openTasks : Results<Task>!
var completedTasks : Results<Task>!

selectedList是經過TaskListsViewController傳遞過來的選擇的任務鏈表。爲了區分任務的完成狀態,定義了兩個變量openTasks、completedTasks。使用Realm的神奇函數filter()能夠實現區分。在解釋如何篩選前,先看下代碼:

func readTasksAndUpateUI(){
    
    completedTasks = self.selectedList.tasks.filter("isCompleted = true")
    openTasks = self.selectedList.tasks.filter("isCompleted = false")
    
    self.tasksTableView.reloadData()
}

在這個函數裏面咱們經過使用Realm提供的filter()方法涉嫌告終果的區分篩選。該方法能被 List、Result、Object實例進行調用,並根據設置的字符串條件返回咱們期待的結果。你能夠將該函數想象成NSPredicate,兩個基本上是同樣的功能。你也能夠經過篩選條件建立NSPredicate來完成同樣的功能。

下面是一個示例:

//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)

在上面的代碼中,咱們篩選了那些紅色而且名字以"BMW"開頭的車。第一行和第二行代碼篩選出來的結果是同樣的。下面的表中列出了經常使用的篩選比較操做符:

clipboard.png

排序

到目前爲止我已經解釋了Realm數據庫的基本操做,在結束教程以前還有一個特徵我想介紹給你們。排序是Realm提供的另外一個很是有用的特性。在List、Result中你能夠調用排序算法("sore criteria")對一組數據進行排序。下面咱們看看如何使用字母或者建立時間來進行排序。首先咱們在UI上添加一個segmented控件,排序會依據用戶進行。

clipboard.png

代碼實現:

@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的一些基本操做,例如Reading、Writing、Updating、Deletion。還有一些更高級的話題很值得你本身去探索學習,最好的方法就是去官方網站看官方文檔。

整個演示的完整代碼

相關文章
相關標籤/搜索