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

《【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 这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。

《【iOS 开发】Realm Swift 数据库使用汇总》 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 文件就可以看到数据了。

《【iOS 开发】Realm Swift 数据库使用汇总》 .realm 文件

2. 真机调试

  • 如果是真机调试的话,打开 Xcode ,选择菜单 Window 下的 Devices

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

  • 选择对应的设备与项目,点击 Download Container

《【iOS 开发】Realm Swift 数据库使用汇总》 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 ,下面的表格提供了关于声明模型属性的简易参考。

类型非可选值形式可选值形式
Booldynamic var value = falselet value = RealmOptional<Bool>()
Intdynamic var value = 0let value = RealmOptional<Int>()
Floatdynamic var value: Float = 0.0let value = RealmOptional<Float>()
Doubledynamic var value: Double = 0.0let value = RealmOptional<Double>()
Stringdynamic var value = ""dynamic var value: String? = nil
Datadynamic var value = NSData()dynamic var value: NSData? = nil
Datedynamic var value = NSDate()dynamic var value: NSDate? = nil
Object必须是可选值dynamic var value: Class?
Listlet value = List<Class>()必须是非可选值
LinkingObjectslet 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

    原文作者:Jonzzs
    原文地址: https://www.jianshu.com/p/06782df0b901
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞