Swift - Realm数据库的使用详解(附样例)

2019年12月11日 阅读数:58
这篇文章主要向大家介绍Swift - Realm数据库的使用详解(附样例),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。


原文连接:点击打开连接
1,什么是Realm
Realm于2014 年7月发布,是一个跨平台的移动数据库引擎,专门为移动应用的数据持久化而生。其目的是要取代Core Data和SQLite。

2,关于Realm,你要知道下面几点:
(1)使用简单,大部分经常使用的功能(好比插入、查询等)均可以用一行简单的代码轻松完成,学习成本低。
(2)Realm不是基于Core Data,也不是基于SQLite封装构建的。它有本身的数据库存储引擎。
(3)Realm具备良好的跨平台特性,能够在iOS和Android平台上共同使用。代码可使用 Swift 、 Objective-C 以及 Java 语言来编写。
(4)Realm 还提供了一个轻量级的数据库查看工具(Realm Browser)。你也能够用它进行一些简单的编辑操做(好比插入和删除操做) 

3,支持的类型
(1)Realm支持如下的属性类型:Bool、Int八、Int1六、Int3二、Int6四、Double、Float、String、NSDate(精度到秒)以及NSData.
(2)也可使用List<object> 和Object来创建诸如一对多、一对一之类的关系模型,此外Object的子类也支持此功能。

4,Realm的安装配置 
(1)先去Realm的官网去下载最新框架: http://static.realm.io/downloads/swift/latest
(2)拖拽RealmSwift.framework和Realm.framework文件到”Embedded Binaries”选项中。选中Copy items if needed并点击Finish
原文:Swift - Realm数据库的使用详解(附样例)


5,将数据插入到数据库中
下面代码判断默认数据库中是否有数据,若是没有的话将几个自定义对像插入到数据库中。
(1)这里以我的消费记录为例,咱们先定义消费类别类,和具体消费记录类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import  RealmSwift
 
//消费类型
class  ConsumeType : Object  {
     //类型名
     dynamic  var  name =  ""
}
 
//消费条目
class  ConsumeItem : Object  {
     //条目名
     dynamic  var  name =  ""
     //金额
     dynamic  var  cost = 0.00
     //时间
     dynamic  var  date =  NSDate ()
     //所属消费类别
     dynamic  var  type: ConsumeType ?
}

(2)判断数据库记录是否为空,空的话则插入数据库(这里以默认数据库为例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import  UIKit
import  RealmSwift
 
class  ViewController UIViewController  {
 
     override  func  viewDidLoad() {
         super .viewDidLoad()
         
         //使用默认的数据库
         let  realm = try!  Realm ()
         //查询全部的消费记录
         let  items = realm.objects( ConsumeItem )
         //已经有记录的话就不插入了
         if  items.count>0 {
             return
         }
         
         //建立两个消费类型
         let  type1 =  ConsumeType ()
         type1.name =  "购物"
         let  type2 =  ConsumeType ()
         type2.name =  "娱乐"
         
         //建立三个消费记录
         let  item1 =  ConsumeItem (value: [ "买一台电脑" ,5999.00, NSDate (),type1])  //可以使用数组建立
         
         let  item2 =  ConsumeItem ()
         item2.name =  "看一场电影"
         item2.cost = 30.00
         item2.date =  NSDate (timeIntervalSinceNow: -36000)
         item2.type = type2
         
         let  item3 =  ConsumeItem ()
         item3.name =  "买一包泡面"
         item3.cost = 2.50
         item3.date =  NSDate (timeIntervalSinceNow: -72000)
         item3.type = type1
         
         // 数据持久化操做(类型记录也会自动添加的)
         try! realm.write {
             realm.add(item1)
             realm.add(item2)
             realm.add(item3)
         }
         
         //打印出数据库地址
         print (realm.path)
     }
 
     override  func  didReceiveMemoryWarning() {
         super .didReceiveMemoryWarning()
     }
}

6,使用Realm Browser查看数据库  
(1)默认数据库是应用的 Documents 文件夹下的一个名为“default.realm”。
1
2
//打印出数据库地址
print (realm.path)
(2)使用Realm Browser工具能够很方便的对.realm数据库进行读取和编辑(在App Store中搜索Realm Browser便可下载)。
能够看到,上面的几个对象已经成功的插入到数据库中来。

原文:Swift - Realm数据库的使用详解(附样例)


原文:Swift - Realm数据库的使用详解(附样例)


6,从数据库中读取记录并显示到表格中来
(1)经过查询操做,Realm 将会返回包含 Object 集合的Results实例。Results 的表现和 Array 十分类似,而且包含在 Results 中的对象可以经过索引下标进行访问。 
(2)全部的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才可以读取相应的数据。 
(3)查询结果并非数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。

Realm为什么没法限制查询数量?
一般查询数据库数据时,咱们能够在sql语句中添加一些限制语句(好比 rownumlimittop等)来限制返回的结果集的行数。
但咱们使用Realm会发现,它没有这种分页功能,感受无论查什么都是把全部的结果都捞出来。好比咱们只要User表的前10条数据,那么作法是先查询出全部的User数据,再从结果集中取出前10条数据。
有人可能会担忧,若是数据库中数据很是多,那每次都这么查不会影响性能吗?
其实大可放心,因为Realm都是延迟加载的,只有当属性被访问时,才可以读取相应的数据。不像一般数据库,查询后,查询结果是从数据库拷贝一份出来放在内存中的。而Realm的查询结果应该说是数据库数据的引用,就算你查出来,若是不用也不会占用什么内存。

下面咱们把库里的数据加载出来,并经过表格显示出来。
效果图以下:
原文:Swift - Realm数据库的使用详解(附样例)


代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import  UIKit
import  RealmSwift
 
class  ViewController UIViewController UITableViewDelegate UITableViewDataSource   {
     
     @IBOutlet  weak  var  tableView:  UITableView !
     
     var  dformatter =  NSDateFormatter ()
     
     //保存从数据库中查询出来的结果集
     var  consumeItems: Results < ConsumeItem >?
 
     override  func  viewDidLoad() {
         super .viewDidLoad()
         
         self .dformatter.dateFormat =  "MM月dd日 HH:mm"
         
         self .tableView!.delegate =  self
         self .tableView!.dataSource =  self
         //建立一个重用的单元格
         self .tableView!.registerClass( UITableViewCell . self , forCellReuseIdentifier:  "MyCell" )
         
         //使用默认的数据库
         let  realm = try!  Realm ()
         //查询全部的消费记录
         consumeItems = realm.objects( ConsumeItem )
     }
     
     //在本例中,只有一个分区
     func  numberOfSectionsInTableView(tableView:  UITableView ) ->  Int  {
         return  1;
     }
     
     //返回表格行数(也就是返回控件数)
     func  tableView(tableView:  UITableView , numberOfRowsInSection section:  Int ) ->  Int  {
         return  self .consumeItems!.count
     }
     
     //建立各单元显示内容(建立参数indexPath指定的单元)
     func  tableView(tableView:  UITableView , cellForRowAtIndexPath indexPath:  NSIndexPath )
         ->  UITableViewCell
     {
         //同一形式的单元格重复使用,在声明时已注册
         let  cell =  UITableViewCell (style: . Value1 , reuseIdentifier:  "MyCell" )
         let  item =  self .consumeItems![indexPath.row]
         cell.textLabel?.text = item.name +  " ¥"  String (format:  "%.1f" , item.cost)
         cell.detailTextLabel?.text =  self .dformatter.stringFromDate(item.date)
         return  cell
     }
 
     override  func  didReceiveMemoryWarning() {
         super .didReceiveMemoryWarning()
         // Dispose of any resources that can be recreated.
     }
}

7,支持断言查询(Predicate),这样能够经过条件查询特定数据
同时可使用链式查询数据。
1
2
3
4
5
6
7
8
9
//查询花费超过10元的消费记录(使用断言字符串查询)
consumeItems =  self .realm.objects( ConsumeItem ). filter ( "cost > 10" )
         
//查询花费超过10元的购物记录(使用 NSPredicate 查询)
let  predicate =  NSPredicate (format:  "type.name = '购物' AND cost > 10" )
consumeItems =  self .realm.objects( ConsumeItem ). filter (predicate)
         
//使用链式查询
consumeItems =  self .realm.objects( ConsumeItem ). filter ( "cost > 10" ). filter ( "type.name = '购物'" )
支持的断言:
比较操做数(comparison operand)能够是属性名称或者某个常量,但至少有一个操做数必须是属性名称;
比较操做符 ==、<=、<、>=、>、!=, 以及 BETWEEN 支持 int、long、long long、float、double 以及 NSDate 属性类型的比较,好比说 age == 45;
相等比较 ==以及!=,好比说Results<Employee>().filter("company == %@", company)
比较操做符 == and != 支持布尔属性;
对于 NSString 和 NSData 属性来讲,咱们支持 ==、!=、BEGINSWITH、CONTAINS 以及 ENDSWITH 操做符,好比说 name CONTAINS ‘Ja’;
字符串支持忽略大小写的比较方式,好比说 name CONTAINS[c] ‘Ja’ ,注意到其中字符的大小写将被忽略;
Realm 支持如下复合操做符:“AND”、“OR” 以及 “NOT”。好比说 name BEGINSWITH ‘J’ AND age >= 32;
包含操做符 IN,好比说 name IN {‘Lisa’, ‘Spike’, ‘Hachi’};
==、!=支持与 nil 比较,好比说 Results<Company>().filter("ceo == nil")。注意到这只适用于有关系的对象,这里 ceo 是 Company 模型的一个属性。
ANY 比较,好比说 ANY student.age < 21
注意,虽然咱们不支持复合表达式类型(aggregate expression type),可是咱们支持对对象的值使用 BETWEEN 操做符类型。好比说,Results<Person>.filter("age BETWEEN %@", [42, 43]])。

8,查询结果的排序
1
2
//查询花费超过10元的消费记录,并按升序排列 
consumeItems =  self .realm.objects( ConsumeItem ). filter ( "cost > 10" ).sorted( "cost" )

9,使用List实现一对多关系
List中能够包含简单类型的Object,表面上和可变的Array很是相似。
注意:List 只可以包含 Object 类型,不能包含诸如String之类的基础类型。 
若是打算给咱们的 Person 数据模型添加一个“dogs”属性,以便可以和多个“dogs”创建关系,也就是代表一个 Person 能够有多个 Dog,那么咱们能够声明一个List类型的属性:
1
2
3
4
5
6
7
8
9
10
class  Person Object  {
     ...  // 其他的属性声明
     let  dogs =  List < Dog >()
}
 
// 这里咱们就可使用已存在的狗狗对象来完成初始化
let  aPerson =  Person (value: [ "李四" , 30, [aDog, anotherDog]])
 
// 还可使用多重嵌套
let  aPerson =  Person (value: [ "李四" , 30, [[ "小黑" , 5], [ "旺财" , 6]]])
能够和以前同样,对 List 属性进行访问和赋值:
1
2
3
let  someDogs =  Realm ().objects( Dog ). filter ( "name contains '小白'" )
ZhangSan .dogs.extend(someDogs)
ZhangSan .dogs.append(dahuang)
反向关系(Inverse Relationship)
经过反向关系(也被称为反向连接(backlink)),您能够经过一个特定的属性获取和给定对象有关系的全部对象。好比说,对 Dog 的实例调用Object().linkingObjects(_:forProperty:) 会返回对应的与被调用实例所指定属性的类有关系的全部对象。经过在 Dog 中定义一个只读(计算)属性 owners 能够简化这个操做:
1
2
3
4
5
6
7
8
9
class  Dog Object  {
     dynamic  var  name =  ""
     dynamic  var  age = 0
     var  owners: [ Person ] {
         // Realm 并不会存储这个属性,由于这个属性只定义了 getter
         // 定义“owners”,和 Person.dogs 创建反向关系
         return  linkingObjects( Person . self , forProperty:  "dogs" )
     }
}

10,添加主键(Primary Keys) 
重写 Object.primaryKey() 能够设置模型的主键。
声明主键以后,对象将被容许查询,更新速度更加高效,而且要求每一个对象保持惟一性。
一旦带有主键的对象被添加到 Realm 以后,该对象的主键将不可修改。
1
2
3
4
5
6
7
8
class  Person Object  {
   dynamic  var  id = 0
   dynamic  var  name =  ""
 
   override  static  func  primaryKey() ->  String ? {
     return  "id"
   }
}

11,添加索引属性(Indexed Properties)
重写 Object.indexedProperties() 方法能够为数据模型中须要添加索引的属性创建索引:
1
2
3
4
5
6
7
8
class  Book Object  {
   dynamic  var  price = 0
   dynamic  var  title =  ""
 
   override  static  func  indexedProperties() -> [ String ] {
     return  [ "title" ]
   }
}

12,设置忽略属性(Ignored Properties)
重写 Object.ignoredProperties() 能够防止 Realm 存储数据模型的某个属性。Realm 将不会干涉这些属性的常规操做,它们将由成员变量(ivar)提供支持,而且您可以轻易重写它们的 setter 和 getter。
1
2
3
4
5
6
7
8
9
10
11
12
class  Person Object  {
   dynamic  var  tmpID = 0
   var  name:  String  // 计算属性将被自动忽略
     return  "\(firstName) \(lastName)"
   }
   dynamic  var  firstName =  ""
   dynamic  var  lastName =  ""
 
   override  static  func  ignoredProperties() -> [ String ] {
     return  [ "tmpID" ]
   }
}

13,修改更新数据  
(1)直接更新内容
1
2
3
4
// 在一个事务中更新对象
realm.write {
   consumeItem.name =  "去北京旅行"
}
(2)经过主键更新
若是您的数据模型中设置了主键的话,那么您可使用Realm().add(_:update:)来更新对象(当对象不存在时也会自动插入新的对象。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/****** 方式1 ***/
// 建立一个带有主键的“书籍”对象,做为事先存储的书籍
let  cheeseBook =  Book ()
cheeseBook.title =  "奶酪食谱"
cheeseBook.price = 9000
cheeseBook.id = 1
 
// 经过 id = 1 更新该书籍
realm.write {
   realm.add(cheeseBook, update:  true )
}
 
/****** 方式2 ***/
// 假设带有主键值 `1` 的“书籍”对象已经存在
realm.write {
   realm.create( Book . self , value: [ "id" : 1,  "price" : 9000.0], update:  true )
// 这本书的`title`属性不会被改变
}
(3)键值编码 
这个是在运行时才能决定哪一个属性须要更新的时候,这个对于大量更新的对象极为有用。
1
2
3
4
5
6
7
let  persons =  Realm ().objects( Person )
Realm ().write {
   // 更新第一个
   persons.first?.setValue( true , forKeyPath:  "isFirst" )
   // 将每一个人的 planet 属性设置为“地球”
   persons.setValue( "地球" , forKeyPath:  "planet" )
}

14,删除数据
1
2
3
4
5
6
let  cheeseBook = ...  // 存储在 Realm 中的 Book 对象
 
// 在事务中删除一个对象
realm.write {
   realm.delete(cheeseBook)
}
也可以删除数据库中的全部数据
1
2
3
4
// 从 Realm 中删除全部数据
realm.write {
   realm.deleteAll()
}

15,Realm数据库配置 
(1)修改默认的的数据库
经过调用 Realm() 来初始化以及访问咱们的 realm 变量。其指向的是应用的 Documents 文件夹下的一个名为“default.realm”的文件。
经过对默认配置进行更改,咱们可使用不一样的数据库。好比给每一个用户账号建立一个特有的 Realm 文件,经过切换配置,就能够直接使用默认的 Realm 数据库来直接访问各自数据库:
1
2
3
4
5
6
7
8
9
10
11
func  setDefaultRealmForUser(username:  String ) {
   var  config =  Realm . Configuration ()
 
   // 使用默认的目录,可是使用用户名来替换默认的文件名
   config.path = config.path.stringByDeletingLastPathComponent()
                            .stringByAppendingPathComponent(username)
                            .stringByAppendingPathExtension( "realm" )
 
   // 将这个配置应用到默认的 Realm 数据库当中
   Realm . Configuration .defaultConfiguration = config
}
(2)打包进项目里的数据库的使用
若是须要将应用的某些数据(好比配置信息,初始化信息等)打包到一个 Realm 文件中,做为主要 Realm 数据库的扩展,操做以下:
1
2
3
4
5
6
7
8
9
10
11
let  config =  Realm . Configuration (
     // 获取须要打包文件的路径
     path:  NSBundle .mainBundle().pathForResource( "MyBundledData" , ofType: "realm" ),
     // 以只读模式打开文件,由于应用数据包并不可写
     readOnly:  true )
 
// 经过配置打开 Realm 数据库
let  realm =  Realm (configuration: config)
 
// 经过配置打开 Realm 数据库
let  results = realm.objects( Dog ). filter ( "age > 5" )
(3)内存数据库
内存数据库在每次程序运行期间都不会保存数据。可是,这不会妨碍到 Realm 的其余功能,包括查询、关系以及线程安全。 假如您须要灵活的数据读写但又不想储存数据的话,那么内存数据库对您来讲必定是一个不错的选择。
1
let  realm =  Realm (configuration:  Realm . Configuration (inMemoryIdentifier:  "MyInMemoryRealm" ))

16,加密数据库 
(1)加密后的 Realm文件不能跨平台使用(由于NSFileProtection 只有 iOS 才可使用) 
(2)Realm 文件不能在没有密码保护的 iOS 设备中进行加密。为了不这些问题(或者您想构建一个 OS X 的应用),可使用 Realm 提供的加密方法。 
(3)加密过的 Realm 只会带来不多的额外资源占用(一般最多只会比日常慢10%)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*****   在建立 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密   ****/
// 产生随机密钥
let  key =  NSMutableData (length: 64)!
SecRandomCopyBytes (kSecRandomDefault,  UInt (key.length),
     UnsafeMutablePointer < UInt8 >(key.mutableBytes))
 
// 打开加密文件
let  config =  Realm . Configuration (encryptionKey: key)
var  error:  NSError ?
let  realm =  Realm (configuration: config, error: &error)
if  realm ==  nil  {
     // 若是密钥错误,`error` 会提示数据库不可访问
     println ( "Error opening realm: \(error)" )
     return
}
 
// 和往常同样使用 Realm 便可
let  dogs = realm.objects( Dog ). filter ( "name contains 'Fido'" )

17,数据迁移(Migration)
(1)为什么要迁移
好比原来有以下 Person 模型:
1
2
3
4
5
class  Person Object  {
     dynamic  var  firstName =  ""
     dynamic  var  lastName =  ""
     dynamic  var  age = 0
}
假如咱们想要更新数据模型,给它添加一个 fullname 属性, 而不是将“姓”和“名”分离开来。
1
2
3
4
class  Person Object  {
     dynamic  var  fullName =  ""
     dynamic  var  age = 0
}
在这个时候若是您在数据模型更新以前就已经保存了数据的话,那么 Realm 就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,不然当您试图打开这个文件的话 Realm 就会抛出错误。 

(2)如何进行数据迁移
假设咱们想要把上面所声明 Person 数据模型进行迁移。以下所示是最简单的数据迁移的必需流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 在(application:didFinishLaunchingWithOptions:)中进行配置
 
let  config =  Realm . Configuration (
   // 设置新的架构版本。这个版本号必须高于以前所用的版本号
   // (若是您以前从未设置过架构版本,那么这个版本号设置为 0)
   schemaVersion: 1,
 
   // 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用
   migrationBlock: { migration, oldSchemaVersion  in
     // 目前咱们还未进行数据迁移,所以 oldSchemaVersion == 0
     if  (oldSchemaVersion < 1) {
       // 什么都不要作!Realm 会自行检测新增和须要移除的属性,而后自动更新硬盘上的数据库架构
     }
   })
 
// 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象
Realm . Configuration .defaultConfiguration = config
 
// 如今咱们已经告诉了 Realm 如何处理架构的变化,打开文件以后将会自动执行迁移
let  realm =  Realm ()
虽然这个迁移操做是最精简的了,可是咱们须要让这个闭包可以自行计算新的属性(这里指的是 fullName),这样才有意义。 在迁移闭包中,咱们可以调用Migration().enumerate(_:_:) 来枚举特定类型的每一个 Object 对象,而后执行必要的迁移逻辑。注意,对枚举中每一个已存在的 Object 实例来讲,应该是经过访问 oldObject 对象进行访问,而更新以后的实例应该经过 newObject 进行访问:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在 application(application:didFinishLaunchingWithOptions:) 中进行配置
 
Realm . Configuration .defaultConfiguration =  Realm . Configuration (
   schemaVersion: 1,
   migrationBlock: { migration, oldSchemaVersion  in
     if  (oldSchemaVersion < 1) {
       // enumerate(_:_:) 方法遍历了存储在 Realm 文件中的每个“Person”对象
       migration. enumerate ( Person .className()) { oldObject, newObject  in
         // 将名字进行合并,存放在 fullName 域中
         let  firstName = oldObject![ "firstName" as String
         let  lastName = oldObject![ "lastName" as String
         newObject![ "fullName" ] =  "\(firstName) \(lastName)"
       }
     }
   })

18,使用带有 REST API 功能的 Realm 数据库示例
咱们将从 豆瓣FM的API 那里获取一组 JSON 格式的频道数据,而后将它以 Realm Objects 的形式储存到默认的 Realm 数据库里。 
(1)json数据格式以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{  
  "channels": [