SwiftiOS学习

Swift - Realm数据库的使用详解

2018-01-09  本文已影响93人  玺睿siri

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、Int8、Int16、Int32、Int64、Double、Float、String、Date(精度到秒)以及Data.

(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)这里以个人消费记录为例,我们先定义消费类别类,和具体消费记录类

import Foundation

import RealmSwift

//消费类型

class ConsumeType``:``Object {

//类型名

@objc dynamic var name = ``""

}

//消费条目

class ConsumeItem``:``Object {

//条目名

@objc dynamic var name = ``""

//金额

@objc dynamic var cost = 0.00

//时间

@objc dynamic var date = ``Date``()

//所属消费类别

@objc dynamic var type:``ConsumeType``?

}
(2)判断数据库记录是否为空,空的话则插入数据库(这里以默认数据库为例)
import UIKit

import RealmSwift

class ViewController``: ``UIViewController {

override func viewDidLoad() {

super``.viewDidLoad()

//使用默认的数据库

let realm = try! ``Realm``()

//查询所有的消费记录

let items = realm.objects(``ConsumeItem``.``self``)

//已经有记录的话就不插入了

if items.count>0 {

return

}

//创建两个消费类型

let type1 = ``ConsumeType``()

type1.name = ``"购物"

let type2 = ``ConsumeType``()

type2.name = ``"娱乐"

//创建三个消费记录

let item1 = ``ConsumeItem``(value: [``"买一台电脑"``,5999.00,``Date``(),type1]) ``//可使用数组创建

let item2 = ``ConsumeItem``()

item2.name = ``"看一场电影"

item2.cost = 30.00

item2.date = ``Date``(timeIntervalSinceNow: -36000)

item2.type = type2

let item3 = ``ConsumeItem``()

item3.name = ``"买一包泡面"

item3.cost = 2.50

item3.date = ``Date``(timeIntervalSinceNow: -72000)

item3.type = type1

// 数据持久化操作(类型记录也会自动添加的)

try! realm.write {

realm.add(item1)

realm.add(item2)

realm.add(item3)

}

//打印出数据库地址

print``(realm.configuration.fileURL ?? ``""``)

}

override func didReceiveMemoryWarning() {

super``.didReceiveMemoryWarning()

}

}

|

6,Data类型数据的存取
参考另一篇文章:Swift - Realm数据库中图片的插入、读取(Data类型数据的存储)

7,使用Realm Browser查看数据库

(1)默认数据库是应用的 Documents 文件夹下的一个名为“default.realm”。
//打印出数据库地址

print``(realm.configuration.fileURL ?? ``""``)

|

(2)使用 Realm Browser 工具可以很方便的对.realm数据库进行读取和编辑(在 App Store 中搜索 Realm Browser 即可下载)。

可以看到,上面的几个对象已经成功的插入到数据库中来。

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

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

8,从数据库中读取记录并显示到表格中来

(1)通过查询操作,Realm 将会返回包含 Object 集合的 Results 实例。Results 的表现和 Array 十分相似,并且包含在 Results 中的对象能够通过索引下标进行访问。

(2)所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。

(3)查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。

下面我们把库里的数据加载出来,并通过表格显示出来。

效果图如下:

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

代码如下:
import UIKit

import RealmSwift

class ViewController``: ``UIViewController``, ``UITableViewDelegate``, ``UITableViewDataSource {

@IBOutlet weak var tableView: ``UITableView``!

var dformatter = ``DateFormatter``()

//保存从数据库中查询出来的结果集

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!.register(``UITableViewCell``.``self``, forCellReuseIdentifier: ``"MyCell"``)

//使用默认的数据库

let realm = try! ``Realm``()

//查询所有的消费记录

consumeItems = realm.objects(``ConsumeItem``.``self``)

}

//在本例中,只有一个分区

func numberOfSections(``in tableView: ``UITableView``) -> ``Int {

return 1;

}

//返回表格行数(也就是返回控件数)

func tableView(_ tableView: ``UITableView``, numberOfRowsInSection section: ``Int``) -> ``Int {

return self``.consumeItems!.count

}

//创建各单元显示内容(创建参数indexPath指定的单元)

func tableView(_ tableView: ``UITableView``, cellForRowAt indexPath: ``IndexPath``)

-> ``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.string(from: item.date)

return cell

}

override func didReceiveMemoryWarning() {

super``.didReceiveMemoryWarning()

}

}

|

9,查询前N条数据
Realm无法直接限制查询数量。所以我们如果想要查出部分数据(比如前5条记录),也是全部查出来后在结果集中捞取。

//查询并取出前5条数据

let dogs = try! ``Realm``().objects(``Dog``.``self``)

for i ``in 0..<5 {

let dog = dogs[I]

// ...

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

10,支持断言查询(Predicate),这样可以通过条件查询特定数据
同时可以使用链式查询数据。
//查询花费超过10元的消费记录(使用断言字符串查询)

consumeItems = realm.objects(``ConsumeItem``.``self``).``filter``(``"cost > 10"``)

//查询花费超过10元的购物记录(使用 NSPredicate 查询)

let predicate = ``NSPredicate``(format: ``"type.name = '购物' AND cost > 10"``)

consumeItems = realm.objects(``ConsumeItem``.``self``).``filter``(predicate)

//使用链式查询

consumeItems = realm.objects(``ConsumeItem``.``self``).``filter``(``"cost > 10"``).``filter``(``"type.name = '购物'"``)

|

支持的断言:

11,查询结果的排序

//查询花费超过10元的消费记录,并按升序排列

consumeItems = realm.objects(``ConsumeItem``.``self``).``filter``(``"cost > 10"``).sorted(byKeyPath: ``"cost"``)

|

12,使用List实现一对多关系
List 中可以包含简单类型的 Object,表面上和可变的 Array 非常类似。
注意:List 只能够包含 Object 类型,不能包含诸如String之类的基础类型。
如果打算给我们的 Person 数据模型添加一个“dogs”属性,以便能够和多个“dogs”建立关系,也就是表明一个 Person 可以有多个 Dog,那么我们可以声明一个List<dog style="outline: 0px;">类型的属性:</dog>
class Person``: ``Object {

... ``// 其余的属性声明

let dogs = ``List``<``Dog``>()

}

// 这里我们就可以使用已存在的狗狗对象来完成初始化

let aPerson = ``Person``(value: [``"李四"``, 30, [aDog, anotherDog]])

// 还可以使用多重嵌套

let aPerson = ``Person``(value: [``"李四"``, 30, [[``"小黑"``, 5], [``"旺财"``, 6]]])

|

可以和之前一样,对 List 属性进行访问和赋值:

let someDogs = realm.objects(``Dog``.``self``).``filter``(``"name contains '小白'"``)

ZhangSan``.dogs.append(objectsIn: someDogs)

ZhangSan``.dogs.append(dahuang)

|

反向关系(Inverse Relationship)

通过反向关系(也被称为反向链接(backlink)),您可以通过一个特定的属性获取和给定对象有关系的所有对象。 Realm 提供了“链接对象 (linking objects)” 属性来表示这些反向关系。借助链接对象属性,您可以通过指定的属性来获取所有链接到指定对象的对象。

例如,一个 Dog 对象可以拥有一个名为 owners 的链接对象属性,这个属性中包含了某些 Person 对象,而这些 Person 对象在其 dogs 属性中包含了这一个确定的 Dog 对象。您可以将 owners 属性设置为 LinkingObjects 类型,然后指定其关系,说明其当中包含了 Person 对象。
class Dog``: ``Object {

@objc dynamic var name = ``""

@objc dynamic var age = 0

// Realm 并不会存储这个属性,因为这个属性只定义了 getter

// 定义“owners”,和 Person.dogs 建立反向关系

let owners = ``LinkingObjects``(fromType: ``Person``.``self``, property: ``"dogs"``)

}

|

13,添加主键(Primary Keys)

重写 Object.primaryKey() 可以设置模型的主键。

声明主键之后,对象将被允许查询,更新速度更加高效,并且要求每个对象保持唯一性。

一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改。
class Person``: ``Object {

@objc dynamic var id = 0

@objc dynamic var name = ``""

override static func primaryKey() -> ``String``? {

return "id"

}

}

|

14,添加索引属性(Indexed Properties)
重写 Object.indexedProperties() 方法可以为数据模型中需要添加索引的属性建立索引:
class Book``: ``Object {

@objc dynamic var price = 0

@objc dynamic var title = ``""

override static func indexedProperties() -> [``String``] {

return [``"title"``]

}

}

|

15,设置忽略属性(Ignored Properties)

重写 Object.ignoredProperties() 可以防止 Realm 存储数据模型的某个属性。Realm 将不会干涉这些属性的常规操作,它们将由成员变量(var)提供支持,并且您能够轻易重写它们的 setter 和 getter。
class Person``: ``Object {

@objc dynamic var tmpID = 0

var name: ``String { ``// 计算属性将被自动忽略

return "\(firstName) \(lastName)"

}

@objc dynamic var firstName = ``""

@objc dynamic var lastName = ``""

override static func ignoredProperties() -> [``String``] {

return [``"tmpID"``]

}

}

|

16,修改更新数据

(1)直接更新内容

// 在一个事务中更新对象

try! realm.write {

consumeItem.name = ``"去北京旅行"

}

|

(2)通过主键更新
如果您的数据模型中设置了主键的话,那么您可以使用 Realm().add(_:update:) 来更新对象(当对象不存在时也会自动插入新的对象。)
/****** 方式1 ***/

// 创建一个带有主键的“书籍”对象,作为事先存储的书籍

let cheeseBook = ``Book``()

cheeseBook.title = ``"奶酪食谱"

cheeseBook.price = 9000

cheeseBook.id = 1

// 通过 id = 1 更新该书籍

try! realm.write {

realm.add(cheeseBook, update: ``true``)

}

/****** 方式2 ***/

// 假设带有主键值1的“书籍”对象已经存在

try! realm.write {

realm.create(``Book``.``self``, value: [``"id"``: 1, ``"price"``: 22], update: ``true``)

// 这本书的title属性不会被改变

}

(3)键值编码

这个是在运行时才能决定哪个属性需要更新的时候,这个对于大量更新的对象极为有用。

let persons = realm.objects(``Person``.``self``)

try! realm.write {

// 更新第一个

persons.first?.setValue(``true``, forKeyPath: ``"isFirst"``)

// 将每个人的 planet 属性设置为“地球”

persons.setValue(``"地球"``, forKeyPath: ``"planet"``)

}

|

17,删除数据
let cheeseBook = ... ``// 存储在 Realm 中的 Book 对象

// 在事务中删除一个对象

try! realm.write {

realm.delete(cheeseBook)

}

也能够删除数据库中的所有数据

// 从 Realm 中删除所有数据

try! realm.write {

realm.deleteAll()

}

|

18,Realm数据库配置

(1)修改默认的的数据库

通过调用 Realm() 来初始化以及访问我们的 realm 变量。其指向的是应用的 Documents 文件夹下的一个名为“default.realm”的文件。

通过对默认配置进行更改,我们可以使用不同的数据库。比如给每个用户帐号创建一个特有的 Realm 文件,通过切换配置,就可以直接使用默认的 Realm 数据库来直接访问各自数据库:
func setDefaultRealmForUser(username: ``String``) {

var config = ``Realm``.``Configuration``()

// 使用默认的目录,但是使用用户名来替换默认的文件名

config.fileURL = config.fileURL!.deletingLastPathComponent()

.appendingPathComponent(``"\(username).realm"``)

// 将这个配置应用到默认的 Realm 数据库当中

Realm``.``Configuration``.defaultConfiguration = config

}

(2)打包进项目里的数据库的使用
如果需要将应用的某些数据(比如配置信息,初始化信息等)打包到一个 Realm 文件中,作为主要 Realm 数据库的扩展,操作如下:

let config = ``Realm``.``Configuration``(

// 获取需要打包文件的 URL 路径

fileURL: ``Bundle``.main.url(forResource: ``"MyBundledData"``, withExtension: ``"realm"``),

// 以只读模式打开文件,因为应用数据包并不可写

readOnly: ``true``)

// 通过配置打开 Realm 数据库

let realm = try! ``Realm``(configuration: config)

// 通过配置打开 Realm 数据库

let results = realm.objects(``Dog``.``self``).``filter``(``"age > 5"``)

|

(3)内存数据库
内存数据库在每次程序运行期间都不会保存数据。但是,这不会妨碍到 Realm 的其他功能,包括查询、关系以及线程安全。 假如您需要灵活的数据读写但又不想储存数据的话,那么内存数据库对您来说一定是一个不错的选择。
let realm = try! ``Realm``(configuration: ``Realm``.``Configuration``(inMemoryIdentifier: ``"MyInMemoryRealm"``))

19,加密数据库

(1)加密后的 Realm文件不能跨平台使用(因为 NSFileProtection 只有 iOS 才可以使用)

(2)Realm 文件不能在没有密码保护的 iOS 设备中进行加密。为了避免这些问题(或者您想构建一个 OS X 的应用),可以使用 Realm 提供的加密方法。

(3)加密过的 Realm 只会带来很少的额外资源占用(通常最多只会比平常慢10%)。
/***** 在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密 ****/

// 产生随机密钥

var key = ``Data``(count: 64)

_ = key.withUnsafeMutableBytes { bytes ``in

SecRandomCopyBytes``(kSecRandomDefault, 64, bytes)

}

// 打开加密文件

let config = ``Realm``.``Configuration``(encryptionKey: key)

let realm:``Realm

do {

realm = try ``Realm``(configuration: config)

} catch ``let error ``as NSError {

// 如果密钥错误,error会提示数据库不可访问

fatalError(``"Error opening realm: \(error)"``)

}

// 和往常一样使用 Realm 即可

let dogs = realm.objects(``Book``.``self``).``filter``(``"name contains 'Fido'"``)
20,数据迁移(Migration)
(1)为何要迁移
比如原来有如下 Person 模型:
class Person``: ``Object {

@objc dynamic var firstName = ``""

@objc dynamic var lastName = ``""

@objc dynamic var age = 0

}

假如我们想要更新数据模型,给它添加一个 fullname 属性, 而不是将“姓”和“名”分离开来。

class Person``: ``Object {

@objc dynamic var fullName = ``""

@objc dynamic var age = 0

}

在这个时候如果您在数据模型更新之前就已经保存了数据的话,那么 Realm 就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,否则当您试图打开这个文件的话 Realm 就会抛出错误。

(2)如何进行数据迁移
假设我们想要把上面所声明 Person 数据模型进行迁移。如下所示是最简单的数据迁移的必需流程:

// 在(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 = try! ``Realm``()

|

虽然这个迁移操作是最精简的了,但是我们需要让这个闭包能够自行计算新的属性(这里指的是 fullName),这样才有意义。 在迁移闭包中,我们能够调用Migration().enumerateObjects(::) 来枚举特定类型的每个 Object 对象,然后执行必要的迁移逻辑。注意,对枚举中每个已存在的 Object 实例来说,应该是通过访问 oldObject 对象进行访问,而更新之后的实例应该通过 newObject 进行访问:
// 在 application(application:didFinishLaunchingWithOptions:) 中进行配置

Realm``.``Configuration``.defaultConfiguration = ``Realm``.``Configuration``(

schemaVersion: 1,

migrationBlock: { migration, oldSchemaVersion ``in

if (oldSchemaVersion < 1) {

// enumerateObjects(ofType:_:) 方法遍历了存储在 Realm 文件中的每一个“Person”对象

migration.enumerateObjects(ofType: ``Person``.className()) { oldObject, newObject ``in

// 将名字进行合并,存放在 fullName 域中

let firstName = oldObject![``"firstName"``] ``as``! ``String

let lastName = oldObject![``"lastName"``] ``as``! ``String

newObject![``"fullName"``] = ``"\(firstName) \(lastName)"

}

}

})

21,使用带有 REST API 功能的 Realm 数据库示例
我们将从 豆瓣FM的API 那里获取一组 JSON 格式的频道数据,然后将它以 Realm Objects 的形式储存到默认的 Realm 数据库里。

(1)json数据格式如下:
{

"channels": [

{

"name_en": "Personal Radio",

"seq_id": 0,

"abbr_en": "My",

"name": "私人兆赫",

"channel_id": 0

},

{

"name": "华语",

"seq_id": 0,

"abbr_en": "",

"channel_id": "1",

"name_en": ""

},

{

"name": "欧美",

"seq_id": 1,

"abbr_en": "",

"channel_id": "2",

"name_en": ""

}

]

}

|

(2)我们将直接把 Dictionary 插入到 Realm 中,然后让 Realm 自行快速地将其映射到 Object 上。

(从 iOS9 起,新特性要求App访问网络请求,要采用 HTTPS 协议。直接请求HTTP数据会报错,解决办法可以参照的我另一篇文章:Swift - 网络请求报App Transport Security has blocked a cleartext错
为了确保示例能够成功,我们需要一个所有属性完全匹配 JSON 键结构的 Object 结构体。如果 JSON 的键结构不匹配 Object 结构体属性结构的话,那么就会在插入时被忽略。

import UIKit

import RealmSwift

class ViewController``: ``UIViewController {

override func viewDidLoad() {

super``.viewDidLoad()

// 调用API

let url = ``URL``(string: ``"[http://www.douban.com/j/app/radio/channels](http://www.douban.com/j/app/radio/channels)"``)!

let response = try! ``Data``(contentsOf: url)

// 对 JSON 的回应数据进行反序列化操作

let json = try! ``JSONSerialization``.jsonObject(with: response,

options: .allowFragments) ``as``! [``String``:``Any``]

let channels = json[``"channels"``] ``as``! [[``String``:``Any``]]

let realm = try! ``Realm``()

try! realm.write {

// 为数组中的每个元素保存一个对象(以及其依赖对象)

for channel ``in channels {

if channel[``"seq_id"``] ``as``! ``Int == 0 {``continue``} ``//第一个频道数据有问题,丢弃掉

realm.create(``DoubanChannel``.``self``, value: channel, update: ``true``)

}

}

print``(realm.configuration.fileURL ?? ``""``)

}

override func didReceiveMemoryWarning() {

super``.didReceiveMemoryWarning()

}

}

//豆瓣频道

class DoubanChannel``:``Object {

//频道id

@objc dynamic var channel_id = ``""

//频道名称

@objc dynamic var name = ``""

//频道英文名称

@objc dynamic var name_en = ``""

//排序

@objc dynamic var seq_id = 0

@objc dynamic var abbr_en = ``""

//设置主键

override static func primaryKey() -> ``String``? {

return "channel_id"

}

}

(3)可以看到数据已经成功插入到库中了

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

22,当前版本的限制

Realm 致力于平衡数据库读取的灵活性和性能。为了实现这个目标,在 Realm 中所存储的信息的各个方面都有基本的限制。例如:

(1)类名称的长度最大只能存储 57 个 UTF8 字符。

(2)属性名称的长度最大只能支持 63 个 UTF8 字符。

(3)NSData 以及 String 属性不能保存超过 16 MB 大小的数据。如果要存储大量的数据,可通过将其分解为16MB 大小的块,或者直接存储在文件系统中,然后将文件路径存储在 Realm 中。如果您的应用试图存储一个大于 16MB 的单一属性,系统将在运行时抛出异常。

(4)对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在 0~591 之间)。

原文出自:www.hangge.com 转载请保留原文链接:http://www.hangge.com/blog/cache/detail_891.html

上一篇 下一篇

猜你喜欢

热点阅读