使用Realm和Swift编写一个ToDo应用git
做者:HOSSAM GHAREEB,时间:2015/11/28
翻译:BigNerdCoding, 若有错误欢迎指出。原文连接github
在去年智能手机的大更新以后,不少工具也同时被开发出来了。这些工具让咱们开发者发布一个高性能、高质量的应用的变的更加简单了。在应用商店得到高排名以及再也不很容易。并且让应用更容易拓展也是很困难的一件事。当你的应用成功的拥有百万级别的用户的时候,你须要注意应用中的全部事情以及全部操做。所以,如今每一个开发者都须要面临的一个问题就是处理数据库。而这又是一件让人感到很是头疼的事情,大多数的开发者会在SQLite和Core Data中挑选一个。曾经我是Core Data的拥趸,由于它在处理数据以及持久化数据方面功能很是强大。可是后来我发现使用Core Data会浪费不少时间。如今我会使用Realm,该框架可以很好的替换SQLite和Core Data。算法
Realm是一个跨平台的手机端数据库支持iOS(Swift和Object双语言版本)、安卓。相比于SQLite和Core Data更好也更快。除此以外,它的使用也很方便之须要几行代码就能够搞定。Realm是一个开源产品你能够免费试用。Realm之因此会出现是由于在过去的十年中移动数据库没有任何可喜的更新。过去在处理移动数据库的时候,你几乎只能选择SQLite或者在其基础上进行封装的Core Data。由于Realm并非一个ORM(对象关系映射)且有本身的持久化引擎使得Realm容易使用而且拥有很好的性能和速度。数据库
Realm快的难以置信而且易用,你能看见任何你须要的东西,而且只须要几行代码就能够完成数据库的读写操做。这里我会列出全部在移动端使用Realm的好处和理由:swift
安装简单:在后面你会发现安装Realm比你想象的还要简单,之须要在Cocoapods中添加简单的命令就能使用了。数组
速度:Realm远快过于SQLite和Core 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以前请确保你已经安装了CocoaPods,咱们须要使用它在Xcode工程中安装Realm。若是你对CocoaPods不熟悉的话,你能够去官网操做安装教程。
如今,咱们建立一个"Single View Application"模版的工程,并将工程命名为“RealmTasks”或者你喜欢的名称。请确保使用的是Swift语言。接下来咱们在终端中切换到当前工程的目录并按照下面步骤初始化工程的CocoaPods。
pod init
使用编辑器生成的文件podfile,并在文件中添加以下内容:
接下来运行命令"pod install"去下载安装Realm到你的工程里面。当安装完成后,你会发现文件夹下面又一个新的Xcode workspace被建立了。打开RealmTasks.xcworkspace文件,你会看见以下界面:
如今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,以下图:
在弹出的窗口中选择你须要安装的类型,并在搜索框中输入对应的插件、模版或者主题。咱们选择Plugins,输入"Realm",在出现的结果里面选择"RealmPlugin"并安装。以下图:
此处可能在Xcode7.1以上版本会出现一些问题,解决方法
最后一个工具是Realm Browser。该浏览器能够帮助你查看或者编辑你的.realm数据库文件。这些数据文件在你的应用中被建立出来,而且包含了里面的实体、属性、以及数据表中的纪录。这些文件如以前所说的同样能够在像iOS、Android这样不一样的平台之间分享。你能够在iTunes store下载到最新版本的工具。打开该应用选择Tools -> Genetate demo database,应用会为你新建一个测试数据库文件你能够在浏览器中看到全部的纪录。以下图:
正如上图显示的,类RealmTestClass1有1000条纪录以及不一样类型的参数(列)。咱们会在下面接受它支持的类型。
如今一切准备工做都已经完成了。开始编码吧。
游戏开始了!首先咱们须要新建一个模型类。能够经过建立一个继承与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。如图:
选择Swift语言,类名为Task。以下图:
如今为该类添加属性。
咱们须要在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或者更高的版本中运行,你会看见下面截图同样的界面:
在这个工程中,我添加了两个视图控制器: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对象:
使用Realm类的实例化方法建立wish1并设置属性
经过传递键值类型的字典类型的属性来建立wish2。
经过传递一个数组来建立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数据库。你能够以下建立该实例:
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。如今当你向左滑动或者点击编辑的时候界面会是下面这样:
上面是关于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会相应的更新该对象。不然直接插入。
若是你是一个有经验的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名称的时候执行。咱们经过设置属性名来完成更新操做。
咱们已经看过了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"开头的车。第一行和第二行代码筛选出来的结果是同样的。下面的表中列出了经常使用的筛选比较操做符:
到目前为止我已经解释了Realm数据库的基本操做,在结束教程以前还有一个特征我想介绍给你们。排序是Realm提供的另外一个很是有用的特性。在List、Result中你能够调用排序算法("sore criteria")对一组数据进行排序。下面咱们看看如何使用字母或者建立时间来进行排序。首先咱们在UI上添加一个segmented控件,排序会依据用户进行。
代码实现:
@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。还有一些更高级的话题很值得你本身去探索学习,最好的方法就是去官方网站看官方文档。
整个演示的完整代码。