【iOS 开发】Realm Swift 数据库使用汇总

Realm

由于最近公司需要将项目用 Swift 改写,项目中需要大量使用数据库,之前 OC 使用的是 Core DataCore Data 使用起来确实十分的繁琐,故决定在 Swift 中弃用,改用 Realm 数据库,下面将使用方法记录下来方便以后查看。


Realm 的优点

Realm 不是基于 Core Data,也不是基于 SQLite 封装构建的。它有自己的数据库存储引擎,下面说一下 Realm 的一些优点。

  • 跨平台: 现在很多应用都是要兼顾 iOSAndroid 两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用 Realm 提供的 API,可以使数据持久化层在两个平台上无差异化的转换。代码可以使用 SwiftObjective-C 以及 Java 语言来编写。

  • 简单易用: Core DataSQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用 Realm,则可以极大地减少学习成本,立即学会本地化存储的方法。大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了,这是 中文官方文档地址

  • 可视化: Realm 还提供了一个轻量级的数据库查看工具,在 Mac Appstore 可以下载 Realm Browser 这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。

Realm Browser

Realm Swift 的安装

这是 RealmGitHub 地址 ,其他方法我就不说了,我是用 CocoaPods 方式安装的,所以就只说 CocoaPods 的安装方法了。

  • 安装 CocoaPods 0.39.0 或者更高版本

  • 运行 pod repo update ,以确保 CocoaPods 能够获取到 Realm 的最新版本。

  • 在你的 Podfile 中,添加 use_frameworks!pod 'RealmSwift' 到你的主要和测试目标。

  • 如果你使用的是 Xcode 8,那么将下面代码复制到你的 Podfile 底部,以便在必要的时候更新 Swift 的版本。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.0'
    end
  end
end
  • 在终端运行 pod install

  • 采用 CocoaPods 生成的 .xcworkspace 来运行工程。

  • 在需要使用 Realm Swift 的地方加入 import RealmSwift

Realm Browser 的使用

先说一下 Realm Browser 这个数据库查看工具的使用方法。

1. 模拟器调试

  • 如果是使用模拟器进行调试,首先通过以下代码打印出 Realm 数据库地址。
let realm = try! Realm()
print(realm.configuration.fileURL!)
  • 然后打开 Finder 按下 command + shift + G 跳转到对应路径下,用 Realm Browser 打开对应的 .realm 文件就可以看到数据了。
.realm 文件

2. 真机调试

  • 如果是真机调试的话,打开 Xcode ,选择菜单 Window 下的 Devices
Devices
  • 选择对应的设备与项目,点击 Download Container
Download Container
  • 导出 xcappdata 文件后,显示包内容,进到 AppData 下的 Documents ,使用 Realm Browser 打开 .realm 文件即可。

Realm Swift 的使用

1. 配置 Realm 数据库

  • 将以下代码写在 AppDelegatedidFinishLaunchingWithOptions 方法中,这个方法主要用于数据模型属性增加或删除时的数据迁移,每次模型属性变化时,将 schemaVersion1 即可,Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构,移除属性的数据将会被删除。
/* Realm 数据库配置,用于数据库的迭代更新 */
let schemaVersion: UInt64 = 0
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
    
    /* 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构 */
    if (oldSchemaVersion < schemaVersion) {}
})
Realm.Configuration.defaultConfiguration = config
Realm.asyncOpen { (realm, error) in
    
    /* Realm 成功打开,迁移已在后台线程中完成 */
    if let _ = realm {
        
        print("Realm 数据库配置成功")
    }
    /* 处理打开 Realm 时所发生的错误 */
    else if let error = error {
        
        print("Realm 数据库配置失败:\(error.localizedDescription)")
    }
}
  • 如果属性改变后,想要保留原来已存在的数据来更新新的属性值,在属性变化后将 schemaVersion1 ,并将 config 改为如下,其余不变。
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
    
    if (oldSchemaVersion < schemaVersion) {
    
        migration.enumerateObjects(ofType: Dog.className()) { oldObject, newObject in
            
            /* 将 Dog 表中旧的 firstName 和 lastName 属性删除,数据保留合并为 fullName 属性 */
            let firstName = oldObject!["firstName"] as! String
            let lastName = oldObject!["lastName"] as! String
            newObject!["fullName"] = "\(firstName) \(lastName)"
        }
    }
})
  • 如果是只是属性重命名,想保留原来已经存在的数据,重命名以后将 schemaVersion1 ,并将 config 改为如下,其余不变,并且重命名操作应该在调用上面 enumerateObjects(ofType: _:) 之外完成。
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
    
    if (oldSchemaVersion < schemaVersion) {
    
        /* 将 Dog 表的 name 属性重命名为 fullName */
        migration.renameProperty(onType: Dog.className(), from: "name", to: "fullName")
    }
})

2. Model 数据模型

Realm 数据模型是基于标准 Swift 类来进行定义的,使用属性来完成模型的具体定义,Realm 模型对象在形式上基本上与其他 Swift 对象相同,你可以给它们添加您自己的方法和协议,和在其他对象中使用类似。

Realm 支持的属性类

Realm 支持这几种属性类型:BoolInt8Int16Int32Int64DoubleFloatStringNSDate 以及 NSData ,下面的表格提供了关于声明模型属性的简易参考。

类型 非可选值形式 可选值形式
Bool dynamic var value = false let value = RealmOptional<Bool>()
Int dynamic var value = 0 let value = RealmOptional<Int>()
Float dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
Double dynamic var value: Double = 0.0 let value = RealmOptional<Double>()
String dynamic var value = "" dynamic var value: String? = nil
Data dynamic var value = NSData() dynamic var value: NSData? = nil
Date dynamic var value = NSDate() dynamic var value: NSDate? = nil
Object 必须是可选值 dynamic var value: Class?
List let value = List<Class>() 必须是非可选值
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") 必须是非可选值

Model 数据模型创建

下面以 DogPerson 为例,通过简单的继承 Object 或者一个已经存在的模型类,你就可以创建一个新的 Realm 数据模型对象。

普通的数据模型
/// 狗狗的数据模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0
}

/// 狗狗主人的数据模型
class Person: Object {

    dynamic var name: String?
    dynamic var birthdate = NSDate()
}
关系绑定
/// 狗狗的数据模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0
    dynamic var owner: Person?  // 对一关系
}

/// 狗狗主人的数据模型
class Person: Object {

    dynamic var name: String?
    dynamic var birthdate = NSDate()
    let dogs = List<Dog>()  // 对多关系
}
反向关系

如果对多关系属性 Person.dogs 链接了一个 Dog 实例,而这个实例的对一关系属性 Dog.owner 又链接到了对应的这个 Person 实例,那么实际上这些链接仍然是互相独立的。

Person 实例的 dogs 属性添加一个新的 Dog 实例,并不会将这个 Dog 实例的 owner 属性自动设置为该 Person

但是由于手动同步双向关系会很容易出错,并且这个操作还非常得复杂、冗余,因此 Realm 提供了 链接对象 (linking objects) 属性来表示这些反向关系。

/// 狗狗的数据模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0
    let owner = LinkingObjects(fromType: Person.self, property: "dogs") // 反向关系
}

/// 狗狗主人的数据模型
class Person: Object {

    dynamic var name: String?
    dynamic var birthdate = NSDate()
    let dogs = List<Dog>()  // 对多关系
}
索引属性(Indexed Properties)

重写 Object.indexedProperties() 方法可以为数据模型中需要添加索引的属性建立索引。Realm 支持字符串整数布尔值 以及 NSDate 属性作为索引。对属性进行索引可以减少插入操作的性能耗费,加快比较检索的速度(比如说 = 以及 IN 操作符)

/// 狗狗的数据模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0

    override class func indexedProperties() -> [String] {

        return ["name"]
    }
}
主键(Primary Keys)

重写 Object.primaryKey() 可以设置模型的主键。声明主键之后,对象将允许进行查询,并且更新速度更加高效,而这也会要求每个对象保持唯一性。 一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改。

Realm 可以将 IntString 类型的属性设为主键,但是不支持自增长属性,所以只能自己给主键生成一个唯一的标识,可以使用 UUID().uuidString 方法生成唯一主键。

/// 狗狗的数据模型
class Dog: Object {
    
    dynamic var id = UUID().uuidString
    dynamic var name: String?
    dynamic var age = 0
    
    override class func primaryKey() -> String? {

        return "id" 
    }
}
忽略属性(Ignored Properties)

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

/// 狗狗的数据模型
class Dog: Object {
    
    dynamic var name: String?
    dynamic var age = 0
    
    override class func ignoredProperties() -> [String]? {

        return ["name"] 
    }
}

3. 创建数据模型对象

  • 可以用多种方法创建一个新的对象:
/* (1) 创建一个狗狗对象,然后设置其属性 */
var myDog = Dog()
myDog.name = "大黄"
myDog.age = 10

/* (2) 通过字典创建狗狗对象 */
let myOtherDog = Dog(value: ["name" : "豆豆", "age": 3])

/* (3) 通过数组创建狗狗对象 */
let myThirdDog = Dog(value: ["豆豆", 5])
  • 即使是数组以及字典的多重嵌套,Realm 也能够轻松完成对象的创建。注意 List 只能够包含 Object 类型,不能包含诸如 String 之类的基础类型。
/* 这里我们就可以使用已存在的狗狗对象来完成初始化 */
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])

/* 还可以使用多重嵌套 */
let anotherPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])

4. 数据库操作(增删改查)

任何操作都需要获取 Realm 实例,每个线程只需要使用一次即可。

/* 获取默认的 Realm 实例,每个线程只需要使用一次即可 */
let realm = try! Realm()

增加数据

/* 创建一个 Dog 对象 */
let dog = Dog(value: ["name" : "豆豆", "age": 3])

/* 创建一个 Dog 对象数组 */
let dogs = [Dog(value: ["name": "张三", "age": 1]), Dog(value: ["name": "李四", "age": 2]), Dog(value: ["name": "王五", "age": 3])]

/* 通过事务将数据添加到 Realm 中 */
try! realm.write {

    realm.add(dog) // 增加单个数据
    realm.add(dogs) // 增加多个数据
    realm.create(Dog.self, value: ["name" : "豆豆", "age": 3], update: true) // 直接根据 JSON 数据增加
}

删除数据

// let dog = ... 存储在 Realm 中的 Dog 对象
// let dogs = ... 存储在 Realm 中的多个 Dog 对象

/* 在事务中删除数据 */
try! realm.write {

    realm.delete(dog) // 删除单个数据
    realm.delete(dogs) // 删除多个数据
    realm.deleteAll() // 从 Realm 中删除所有数据
}

修改数据

  • 内容直接更新: 在事务中直接修改某一条数据。
// let dog = ... 存储在 Realm 中的 Dog 对象

/* 在一个事务中修改数据 */
try! realm.write {

    dog.name = "张三"
}
  • ** 通过主键更新: ** 如果你的数据模型中设置了主键的话,那么你可以使用 Realm().add(_:update:) 来更新数据,如果数据不存在时会自动插入新的数据。
// let dog = ... 存储在 Realm 中的 Dog 对象(有主键)
// let dogs = ... 存储在 Realm 中的多个 Dog 对象(有主键)

/* 在一个事务中修改数据 */
try! realm.write {

    realm.add(dog, update: true) // 更新单个数据
    realm.add(dogs, update: true) // 更新多个数据
}
  • 键值编码: ObjectResult 以及 List 都遵守 键值编码(Key-Value Coding) 机制。 当你在运行时才能决定哪个属性需要更新的时候,这个方法是最有用的。将 KVC 应用在集合当中是大量更新对象的极佳方式,这样就可以不用经常遍历集合,为每个项目创建一个访问器了。
// let dogs = ... 存储在 Realm 中的多个 Dog 对象

/* 在一个事务中修改数据 */
try! realm.write {

    dogs.first?.setValue("张三", forKeyPath: "name") // 将第一个狗狗名字改为张三
    dogs.setValue("张三", forKeyPath: "name") // 将所有狗狗名字都改为张三
}

查询数据

  • 普通查询: 查询数据库中某张表的所有数据。
/* 从数据库中查询所有狗狗 */
let dogs = realm.objects(Dog.self)
  • 主键查询: 根据 主键 查询某张表的某条数据,模型必须包含主键,否则会崩溃。
/* 从数据库中查询主键为 1 的狗狗 */
let dog = realm.object(ofType: Dog.self, forPrimaryKey: "1")
  • 条件查询: 根据 断言字符串 或者 NSPredicate 谓词 查询某张表中的符合条件数据。
/* 根据断言字符串从数据库查询 name 为 张三 的狗狗 */
var dogs = realm.objects(Dog.self).filter("name = %@", "张三")

/* 根据 NSPredicate 谓词从数据库查询 age 小于 5 并且 name 以 ‘张’ 开头的狗狗 */
let predicate = NSPredicate(format: "age < 5 AND name BEGINSWITH '张'")
var dogs = realm.objects(Dog.self).filter(predicate)
  • 排序查询: 将查询结果进行排序,可以和条件查询配合使用。
/* 将查询到的狗狗根据名字升序进行排序 */
let dogs = realm.objects(Dog.self).sorted(byKeyPath: "name")

/* 将查询到的狗狗根据名字降序进行排序 */
let dogs = realm.objects(Dog.self).sorted(byKeyPath: "name", ascending: false)

/* 将查询到的狗狗根据名字和年龄升序进行排序 */
let dogs = realm.objects(Dog.self).sorted(by: ["name", "age"])

想要了解更多可以查看 中文官方文档地址 ,有不足之处之后会补充,OC 版本的话可以看这篇文章:Realm数据库 从入门到“放弃” ,写的非常详细,也参考了不少这篇文章的内容。

将来的你,一定会感激现在拼命的自己,愿自己与读者的开发之路无限美好。

我的传送门: 博客简书微博GitHub

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容