Realm 学习笔记 --- RealmSwift

文档


2.8.3
工具 - Realm Browser

说明


内容全部来自文档...只做个人笔记,如需学习请移步官方网站(地址见上面)

数据模型


  • 继承 Object 或一个已有的模型, 可以给模型添加方法和协议
  • 限制: 对象只能在其被创建的那个线程中使用

由于 Realm 中定义的所有模型在程序启动时就会被解析,所以即使代码中没有调用,它们都需要被初始化。
在 Swift 中使用 Realm 的时候,Swift.reflect(_:)函数可用于确定您模型中的信息,这需要确保 init() 已被成功调用。这意味着所有非可选的属性必须添加一个默认值

  • 支持类型

  • Bool、Int8、Int16、Int32、Int64、Double、Float、String、NSDate 以及NSDat

  • String、NSDate 以及 NSData 类型的属性都可以添加可选值

  • Object 类型的属性必须设置为可选

  • 通过 List 类型的属性您可以定义一个对多关系。List中可以包含简单类型的Object

class Person: Object {
    // 其余的属性声明...
    let dogs = List()
}
  • 反向关系(Inverse Relationship):

链接是单向性的。因此,如果对多关系属性 Person.dogs 链接了一个 Dog 实例,而这个实例的对一关系属性 Dog.owner 又链接到了对应的这个 Person 实例,那么实际上这些链接仍然是互相独立的。为 Person 实例的 dogs 属性添加一个新的 Dog 实例,并不会将这个 Dog 实例的 owner 属性自动设置为该 Person。但是由于手动同步双向关系会很容易出错,并且这个操作还非常得复杂、冗余,因此 Realm 提供了“链接对象 (linking objects)” 属性来表示这些反向关系。
借助链接对象属性,您可以通过指定的属性来获取所有链接到指定对象的对象。例如,一个 Dog 对象可以拥有一个名为 owners 的链接对象属性,这个属性中包含了某些 Person 对象,而这些 Person 对象在其 dogs 属性中包含了这一个确定的 Dog 对象。您可以将 owners 属性设置为 LinkingObjects 类型,然后指定其关系,说明其当中包含了 Person 对象

class Dog: Object {
    dynamic var name = ""
    dynamic var age = 0
    let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}
  • 通过使用 RealmOptional 可以声明可空的数值类型
 // 可选的 int 属性,默认为 nil
    // RealmOption 属性应该始终声明为常量
    // 因为目前直接给这个属性进行赋值并不会起任何作用
    let age = RealmOptional()

RealmOptional 支持 Int、Float、Double 以及 Bool 类型,以及支持所有大小的 Int 类型(包括 Int8, Int16, Int32, Int64)

类型 非可选值形式 可选值形式
Bool dynamic var value = false let value = RealmOptional()
Int dynamic var value = 0 let value = RealmOptional()
Float dynamic var value: Float = 0.0 let value = RealmOptional()
Double dynamic var value: Double = 0.0 let value = RealmOptional()
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 n/a: 必须是可选值 dynamic var value: Class?
List let value = List() n/a: 必须是非可选值
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") n/a: 必须是非可选值
  • 索引属性: 重写 Object.indexedProperties()方法可以为数据模型中需要添加索引的属性建立索引. Realm 支持字符串、整数、布尔值以及 NSDate 属性作为索引
class Book: Object {
  dynamic var price = 0
  dynamic var title = ""

  override static func indexedProperties() -> [String] {
    return ["title"]
  }
}

对属性进行索引可以减少插入操作的性能耗费,加快比较检索的速度(比如说 = 以及 IN 操作符)

  • 对象自动更新 => 修改某个对象的属性会立刻影响到其他所有指向同一个对象的实例。
let myPuppy = realm.objects(Dog).filter("age == 1").first
try! realm.write {
  myPuppy!.age = 2
}
  • 主键(Primary keys): 重写 Object.primaryKey()可以设置模型的主键. 声明主键之后,对象将允许进行查询,并且更新速度更加高效,而这也会要求每个对象保持唯一性. 一旦带有主键的对象被添加到 Realm 之后,该对象的主键将不可修改

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

 override static func ignoredProperties() -> [String] {
    return ["tmpID"]
  }

忽略属性发生更改的时候也不会触发通知

集合


  • Results 类, 从检索中返回的对象集合
  • List 类, 模型中的对多关系
  • LinkingObjects 类, 模型中的反向关系
  • RealmCollection 协议,定义了所有 Realm 集合所需要遵守的常用接口。
  • AnyRealmCollection 类型擦除的类,可以向前调用一个具体的 Realm 集合,例如 Results、List 或者 LinkingObjects .

对象存储


对对象的所有更改(添加,修改和删除)都必须通过写入事务(transaction)完成。

  • Realm 写入操作是同步以及阻塞的,并不会异步运行

创建对象

// (1) 创建一个狗狗对象,然后设置其属性
var myDog = Dog()
myDog.name = "大黄"
myDog.age = 10

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

// (3) 通过数组创建狗狗对象
let myThirdDog = Dog(value: ["豆豆", 5])
  • 使用指定初始化器(designated initializer)创建对象是最简单的方式。
  • 通过使用恰当的键值,对象还可以通过字典完成创建。
  • 最后,Object 子类还可以通过数组完成实例化,数组中的值必须和数据模型中对应属性的次序相同

添加数据

            let realm = try! Realm() // 获取默认的 realm 实例
            
            do {
                
                try realm.write {
                    realm.add(user, update: true) // 写事务, 将数据添加到 realm 数据库中
                }
            }
            catch let error {
                
                print("realm 写如错误: \(error.localizedDescription)")
            }

更新数据

  • 内容直接更新
// 在一个事务中更新对象
try! realm.write {
  author.name = "托马斯·品钦"
}
  • 通过主键更新
// 创建一个带有主键的“书籍”对象,作为事先存储的书籍
let cheeseBook = Book()
cheeseBook.title = "奶酪食谱"
cheeseBook.price = 9000
cheeseBook.id = 1
// 通过 id = 1 更新该书籍
try! realm.write {
  realm.add(cheeseBook, update: true)
}

如果主键 id 为1的 Book 对象已经存在于数据库当中了,那么对象就会简单地进行更新。而如果不在数据库中存在的话,那么这个操作将会创建一个新的 Book 对象并添加到数据库当中。
您同时通过传递您想要更新值的集合,从而更新带有主键的某个对象的部分值,比如说如下所示:

// 假设带有主键值 `1` 的“书籍”对象已经存在
try! realm.write {
  realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
// 这本书的`title`属性不会被改变
}

不能够给未定义主键的对象类型传递 update: true
请注意,当对象更新的时候, nil 仍然会被认为是可选属性 的有效值。如果您提供了一个属性值为 nil 的字典,那么这些都会应用到您的对象当中,并且对应的属性都将为空。为了确保不出现任何意外的数据丢失,请在使用此方法的时候再三确认只提供了您想要进行更新的属性。

  • 键值编码
    Object、Result以及 List都遵守键值编码(Key-Value Coding)机制
let persons = realm.objects(Person)
try! realm.write {
  persons.first?.setValue(true, forKeyPath: "isFirst")
  // 将每个人的 planet 属性设置为“地球”
  persons.setValue("地球", forKeyPath: "planet")
}

删除数据

// let cheeseBook = ... 存储在 Realm 中的 Book 对象
// 在事务中删除一个对象
try! realm.write {
  realm.delete(cheeseBook)
}
// 从 Realm 中删除所有数据
try! realm.write {
  realm.deleteAll()
}

删除存储在 Realm 中的所有数据。注意,Realm 文件的大小不会被改变,因为它会保留空间以供日后快速存储数据。

查询


  • 条件查询
  • 排序
  • 链式查询
  • 自动更新

Results 是底层数据的动态表现,其会进行自动更新,这意味着检索到的结果不能进行重复检索。它们会反映出当前线程上 Realm 的当前状态,包括在当前线程上的写事务当中。唯一的例外是,当使用 for...in 遍历时,因为当遍历开始的时候,总会全部遍历完所有匹配该检索的对象,即使某些对象在遍历过程中被过滤器修改或者删除

let puppies = realm.objects(Dog).filter("age < 2")
puppies.count // => 0
try! realm.write {
  realm.create(Dog.self, value: ["name": "大黄", "age": 1])
}
puppies.count // => 1

Realm 数据库


默认的 Realm 数据库

您可能很早就已经注意到,我们总是通过调用 Realm()来初始化以及访问我们的 realm
变量。这个方法将会返回一个 Realm对象,并指向您应用的 Documents (iOS) 或者 Application Support (OS X)文件夹下的一个名为“default.realm”的文件。

Realm 配置

通过Realm.Configuration您可以配置诸如 Realm 文件在何处存储之类的信息
例如为不同用户创建不同的数据库

func setDefaultRealmForUser(username: String) {
  var config = Realm.Configuration()

  // 使用默认的目录,但是使用用户名来替换默认的文件名
  config.fileURL = config.fileURL!.deletingLastPathComponent()
                         .appendingPathComponent("\(username).realm")

  // 将这个配置应用到默认的 Realm 数据库当中
  Realm.Configuration.defaultConfiguration = config
}

其他 Realm 数据库

有的时候,在不同位置存储多个 Realm 数据库是十分有用的。 例如,如果您需要将您应用的某些数据打包到一个 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).filter("age > 5")

请注意,使用自定义 URL 路径来初始化 Realm 数据库需要拥有路径所在位置的写入权限。 通常存储可写 Realm 文件的地方是位于 iOS 上的“Documents”文件夹以及位于 OS X 上的“Application Support”文件夹。 具体情况,请遵循 苹果的 iOS 数据存储指南, 它推荐将文件存储在/Library/Caches
目录下。

内存数据库

通常情况下,Realm 数据库是存储在硬盘中的,但是您能够通过设置 inMemoryIdentifier 而不是设置Realm.Configuration 中的 fileURL 属性,以创建一个完全在内存中运行的数据库。

let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))

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

内存数据库会在临时文件夹中创建多个文件,用来协调处理诸如跨进程通知之类的事务。 实际上没有任何的数据会被写入到这些文件当中,除非操作系统由于内存过满需要清除磁盘上的多余空间。

注意: 当所有具备特定标识符的Realm 内存数据库实例超出可用范围,但是却没有被引用的话,那么_ Realm 内存数据库中的所有数据都会被删除_。我们建议您在应用的生命周期内,保持对 Realm 内存数据库的强引用。(对于存储在硬盘当中的 Realm 数据库而言,这并不是必须的操作。)

异步打开 Realm 数据库

如果在打开 Realm 数据库的时候,需要执行耗时操作的话(例如执行 迁移、压缩 或者下载可同步 Realm 数据库中的远程内容),那么应该是用 asyncOpen
API 来执行所需的操作,以便在 Realm 被发送给指定队列之前,在后台线程中处于可用状态。例如:

let config = Realm.Configuration(schemaVersion: 1, migrationBlock: { migration, oldSchemaVersion in 
// 潜在的冗长数据迁移操作
})Realm.asyncOpen(configuration: config) { realm, error in 
if let realm = realm { 
// Realm 成功打开,迁移已在后台线程中完成 
}
 else if let error = error {
 // 处理打开 Realm 时所发生的错误 
}
}

此外,可同步数据库会等待所有的远程内容都处于可用状态,然后再开始执行下载和本地应用操作。例如:

let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: realmURL))
Realm.asyncOpen(configuration: config) { realm, error in 
if let realm = realm { 
// Realm 成功打开,所有的远程数据都可用
 } else if let error = error {
 // 处理在打开或者下载 Realm 内容时所发生的错误 
}
}

查找 Realm 文件

如果您不知道如何寻找应用的 Realm 文件,那么请查看这个StackOverflow回答来获取详细信息.

数据库迁移

  • 新增删除表,Realm不需要做迁移
  • 新增删除字段,Realm不需要做迁移。Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构

当您使用任意一个数据库时,您随时都可能打算修改您的数据模型。 由于 Realm 的数据模型是以标准的 Swift 类来定义的,这使得修改模型就像修改其他的 Swift 类一样方便。 例如,假设我们有如下 Person
模型:

class Person: Object {
    dynamic var firstName = ""
    dynamic var lastName = ""
    dynamic var age = 0
}

假如我们想要更新数据模型,给它添加一个 fullname
属性, 而不是将“姓”和“名”分离开来。 为此我们只需要改变一下代码即可,范例如下:

class Person: Object {
    dynamic var fullName = ""
    dynamic var age = 0
}

在这个时候如果您在数据模型更新之前就已经保存了数据的话,那么 Realm 就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,否则当您试图打开这个文件的话 Realm 就会抛出错误。
注意在进行迁移的时候, default property values 并不适用于新的对象,也不适用于既有对象的新属性。我们认为这是一个 Bug,我们会在 #1793 对其保持关注。
进行迁移
定义迁移操作
通过设置 Realm.Configuration.schemaVersion
以及 Realm.Configuration.migrationBlock
可以定义一个迁移操作以及与之关联的架构版本。 迁移闭包将会提供提供相应的逻辑操作,以让数据模型从之前的架构转换到新的架构中来。 每当通过配置创建完一个 Realm
之后,迁移闭包将会在迁移需要的时候,将给定的架构版本应用到更新 Realm
操作中。
例如,假设我们想要把上面所声明 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()

我们最起码需要做的,是使用一个空的闭包来更新版本,以表明这个架构已经被 Realm 升级(自动)完毕。
值的更新
虽然这个迁移操作是最精简的了,但是我们需要让这个闭包能够自行计算新的属性(这里指的是 fullName
),这样才有意义。 在迁移闭包中,我们能够调用Migration().enumerateObjects(ofType: _:_:)来枚举特定类型的每个 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)"
      }
    }
  })

一旦迁移成功结束,Realm 文件和其中的所有对象都可被您的应用正常访问。
属性重命名
在迁移过程中对类中某个属性进行重命名操作,比起拷贝值和保留关系来说要更为高效。
要在迁移过程中对某个属性就进行重命名的话,请确保您的新模型当中的这个属性是一个全新的名字,它的名字不能和原有模型当中的名字重合。
如果新的属性拥有不同的可空性或者索引设置的话,这些配置会在重命名操作期间生效。
下面是一个例子,展示了您该如何将 Person
的 yearsSinceBirth
属性重命名为 age
属性:

// 在您的 application(application:didFinishLaunchingWithOptions:) 当中使用
Realm.Configuration.defaultConfiguration = Realm.Configuration(
  schemaVersion: 1,
  migrationBlock: { migration, oldSchemaVersion in
    // 我们目前还没有迁移任何东西,因此 oldSchemaVersion == 0
    if (oldSchemaVersion < 1) {
      // 重命名操作应该在调用 `enumerateObjects(ofType: _:)` 之外完成
      migration.renameProperty(onType: Person.className(), from: "yearsSinceBirth", to: "age")
    }
  })

添加更多版本
假如说现在我们有两个先前版本的 Person类:

// v0
// class Person: Object {
//     dynamic var firstName = ""
//     dynamic var firstName = ""
//     dynamic var age = 0
// }
// v1
// class Person: Object {
//     dynamic var fullName = "" // 新属性
//     dynamic var age = 0
// }
// v2
class Person: Object {
    dynamic var fullName = ""
    dynamic var email = "" // 新属性
    dynamic var age = 0
}

我们的迁移闭包里面的逻辑大致如下:

Realm.Configuration.defaultConfiguration = Realm.Configuration(
  schemaVersion: 2,
  migrationBlock: { migration, oldSchemaVersion in
    // enumerateObjects:block: 遍历了存储在 Realm 文件中的每一个“Person”对象
    migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
      // 只有当 Realm 数据库的架构版本为 0 的时候,才添加 “fullName” 属性
      if oldSchemaVersion < 1 {
        let firstName = oldObject!["firstName"] as! String
        let lastName = oldObject!["lastName"] as! String
        newObject!["fullName"] = "\(firstName) \(lastName)"
      }
      // 只有当 Realm 数据库的架构版本为 0 或者 1 的时候,才添加“email”属性
      if oldSchemaVersion < 2 {
          newObject!["email"] = ""
      }
    }
  })
// Realm 数据库会自动执行此数据迁移,然后成功进行访问
let realm = try! Realm()

要了解关于数据架构迁移如何实现的更完整信息,请参考我们的迁移例程应用。
线性迁移(Linear Migrations)
假如说,我们的应用有两个用户: JP和Tim。
JP经常更新应用,但Tim却经常跳过某些版本。
所以JP可能下载过这个应用的每一个版本,并且一步一步地跟着更新构架:第一次下载更新后,数据库架构从v0更新到v1;第二次架构从v1更新到v2……以此类推,井然有序。
相反,Tim很有可能直接从v0版本直接跳到了v2版本。
因此,您应该使用非嵌套的 if (oldSchemaVersion < X)
结构来构造您的数据库迁移模块,以确保无论用户在使用哪个版本的架构,都能完成必需的更新。
当您的用户不按套路出牌,跳过有些更新版本的时候,另一种情况也会发生。
假如您在v2里删掉了一个“email”属性,然后在v3里又把它重新引进了。假如有个用户从v1直接跳到v3,那 Realm 不会自动检测到v2的这个删除操作,因为存储的数据构架和代码中的构架吻合。
这会导致 Tim 的 Person 对象有一个 v3 的 email 属性,但里面的内容却是 v1 的。
这个看起来没什么大问题,但是假如两者的内部存储类型不同(比如说: 从 ISO email 标准格式变成了自定义格式),那麻烦就大了。为了避免这种不必要的麻烦,我们推荐您在 if (oldSchemaVersion < 3)
语句中,清空所有的 email 属性。

使用


主要使用就是这两个文件, 具体在网络请求以及在什么时候获取/删除缓存数据就不详细说明, 我也在一步一步探索尝试
RNRealmProvider.swift

//
//  RNRealmProvider.swift
//  SpecialHoyoServicer
//
//  Created by roni on 2017/7/5.
//  Copyright © 2017年 roni. All rights reserved.
//

import Foundation
import RealmSwift


//MARK: - Realm 中间层


//MARK: - 增

/// 写事务: 单个对象 -- 也可以通过主键更新整个对象
///
/// - Parameters:
///   - object: 写入对象
///   - isPrimaryKey: 是否有主键  -- 不能够给未定义主键的对象类型传递 update: true
internal func realmWirte(model object: Object, isPrimaryKey: Bool = false) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    do {
        
        try realm.write {
            realm.add(object, update: isPrimaryKey) // 写事务, 将数据添加到 realm 数据库中....
        }
    }
    catch let error {
        
        print("realm 写入错误: \(error.localizedDescription) and Objetct: \(object)")
    }
    
}

/// 写事务: 多个对象 -- 也可以通过主键更新整个对象
///
/// - Parameters:
///   - objects: 写入对象数据
///   - isPrimaryKey: 是否有主键  -- 不能够给未定义主键的对象类型传递 update: true
internal func realmWirteModels(models objects: [Object], isPrimaryKey: Bool = false) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    do {
        
        try realm.write {
            realm.add(objects, update: isPrimaryKey) // 写事务, 将数据添加到 realm 数据库中....
        }
    }
    catch let error {
        
        print("realm 写入错误: \(error.localizedDescription) and Objetct: \(objects)")
    }
    
}



//MARK: - 删

/// 删除单个对象
///
/// - Parameters:
///   - Obj: 类
///   - condition: 满足条件的对象
internal func realmDeleteObject(Model Obj: T.Type, condition: (Results) -> T?) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    let results = realm.objects(Obj)
    
    let model = condition(results)
    guard let m = model else {
        print("没找到符合条件的对象")
        return
    }
    
    do {
        
        try realm.write {
            
            realm.delete(m)
        }
    }
    catch let error {
        
        print("realm 删除错误: \(error.localizedDescription)")
    }
    
}

/// 删除单个对象
///
/// - Parameters:
///   - Obj: 类
///   - condition: 满足条件的对象
internal func realmDeleteObjects(Model Obj: T.Type, condition: (Results) -> Results?) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    let results = realm.objects(Obj)
    
    let models = condition(results)
    guard let m = models else {
        print("没找到符合条件的对象")
        return
    }
    
    do {
        
        try realm.write {
            
            realm.delete(m)
        }
    }
    catch let error {
        
        print("realm 删除错误: \(error.localizedDescription)")
    }
    
}


/// 删除所有 -- 慎用
internal func realmDeleteAll() {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    do {
        
        try realm.write {
            
            realm.deleteAll()
        }
    }
    catch let error {
        
        print("realm 删除错误: \(error.localizedDescription)")
    }
    
}


//MARK: - 改

/// 主键更新
///
/// - Parameters:
///   - Obj: 需要更新的类
///   - value: 需要更新的字段
///   - isPrimaryKey: 是否有主键
internal func realmUpdate(Model Obj: T.Type, value: [String: Any], isPrimaryKey: Bool = false) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    do {
        
        try realm.write {
            realm.create(Obj, value: value, update: isPrimaryKey)
        }
    }
    catch let error {
        
        print("realm 更新错误: \(error.localizedDescription)")
    }
    
}

/// 键值更新 -- 只针对有主键的 model
///
/// - Parameters:
///   - Obj: 需要更新的类
///   - value: 需要更新的  k-v
///   - isPrimaryKey: 主键
internal func realmUpdateByKVC(Model Obj: T.Type, value: [String: Any], primaryKey: Any) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    let model = realm.object(ofType: Obj, forPrimaryKey: primaryKey)
    
    guard let m = model else {
        print("没找到符合条件的对象")
        return
    }
    
    do {
        
        
        try realm.write {
            
            for (k, v) in value {
                m.setValue(v, forKey: k)
            }
        }
    }
    catch let error {
        
        print("realm 更新错误: \(error.localizedDescription)")
    }
    
}

/// 键值更新
///
/// - Parameters:
///   - Obj: 需要更新的类
///   - value: 需要更新的 k-v
///   - condition: 满足条件的对象
internal func realmUpdateByKVCForResults(Model Obj: T.Type, value: [String: Any], condition: (Results) -> T?) {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    let results = realm.objects(Obj)
    
    let model = condition(results)
    guard let m = model else {
        print("没找到符合条件的对象")
        return
    }
    
    do {
        
        try realm.write {
            
            for (k, v) in value {
                m.setValue(v, forKey: k)
            }
        }
    }
    catch let error {
        
        print("realm 更新错误: \(error.localizedDescription)")
    }
    
}


//MARK: - 查

/// 查询对象数组
///
/// - Parameter Obj: 查询的类
/// - Returns: 数组
internal func realmQueryResults(Model Obj: T.Type) -> Results {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    return realm.objects(Obj)
}

/// 查询对象
///
/// - Parameters:
///   - Obj: 查询的类
///   - condition: 满足条件
/// - Returns: models
internal func realmQueryModel(Model Obj: T.Type, condition: (Results) -> Results?) -> Results? {
    
    let realm = try! Realm() // 获取默认的 realm 实例
    
    let results = realm.objects(Obj)
    
    return condition(results)
}

RNRealmMigration.swift

import Foundation
import RealmSwift


// 数据库迁移文件
// 每次更新之后修改!!!!! 特别是发布前!!!!!!!!, 如果有 model 属性的变更必须修改

/**
 * ## 说明 --- [文档](https://realm.io/cn/docs/swift/latest/#migration)
 * 新增删除表,Realm不需要做迁移
 * 新增删除字段,Realm不需要做迁移, 但需要更改版本号schemaVersion。Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构 ---
 * <#item2#>
 * <#item3#>
 */


internal func realmMigration() {
    
    var config = Realm.Configuration(
       
        schemaVersion: 1, // 设置新的架构版本。这个版本号必须高于之前所用的版本号(如果您之前从未设置过架构版本,那么这个版本号设置为 0)
        
        // 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用
        migrationBlock: { migration, oldSchemaVersion in
            if (oldSchemaVersion < 1) {
               
              //  migration.enumerateObjects(ofType: <#T##String#>, <#T##block: (MigrationObject?, MigrationObject?) -> Void##(MigrationObject?, MigrationObject?) -> Void#>) // 值更新
                
             //   migration.renameProperty(onType: <#T##String#>, from: <#T##String#>, to: <#T##String#>) // 重命名属性
            }
    })
    
    config.fileURL = config.fileURL!.deletingLastPathComponent()
        .appendingPathComponent("hoyoservicer-v\(config.schemaVersion).realm")
    Realm.Configuration.defaultConfiguration = config // 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象
    
    // 现在我们已经告诉了 Realm 如何处理架构的变化,打开文件之后将会自动执行迁移
    let _ = try! Realm()
}

补充1

问题:
'RLMException: object has been deleted or invalidated'
描述:
使用过程中,涉及到了分页查询问题,由于目前后台的分页策略是通过 pageindexpagesize 进行分页查询,这样我做了缓存之后,如果用户进入页面之后就进行上拉加载,这时候我给后台的请求 index==1,这样就会导致请求下来的数据与缓存数据重复,即使我把上次请求的pageindex 也缓存下来了,但是后台的数据不断变化,数据会在表中不断被挤进下一页,这样根据之前的 index 是拿不到对的数据的...(之后我们会修改分页策略,暂且不提)..当前可以在拿到数据后进行去重,不过 for for这种双重循环我是不想写,一但数据积累过多速度就无法保证了
现在我做的妥协就是进入页面就自动下拉刷新(如果网络不好加载不了相信上拉加载也没法成功><这里存在 bug,但是只能后续再修改),下拉刷新请求到数据时,我会把满足条件的这类数据全部删除,这里就是导致问题出现的原因,我删除了数据,但是我手贱的点击了某条数据的详情,一定几率的触发了这个 bug...(这里有个奇怪的问题,我的某个分页项出现了我还没用网络请求成功 reload时,就触发了tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell这个方法,我一直找不到问题原因)
这里我有在霜神的文章下询问,也是从某条评论中找到的解决方案(跟霜神的讨论,貌似霜神理解错了我的意思)
方案来源-9楼
我的讨论-64楼
解决方案:
这里我在从数据库拿数据的时候对拿到的数据进行了浅复制,不牵扯到数据库的引用,完完全全的另一份数据,即使我正在删除以前的数据也不会影响我当前的使用...(当然,这里需要自己实现对应 model 的 复制方法)

extension Order: NSCopying{

    func copy(with zone: NSZone? = nil) -> Any {
        let o = Order()
        o.crmId = crmId
        o.state = state
        o.clientName = clientName
        o.clientMobile = clientMobile
        o.userPhone = userPhone
        o.serviceItem = serviceItem
        o.province = province
        o.city = city
        o.country = country
        o.address = address
        o.latitude = latitude
        o.longitude = longitude
        o.productInfo = productInfo
        o.createTime = createTime
        o.checkState = checkState
        o.describle = describle
        o.modifyTime = modifyTime
        o.appointmentTime = appointmentTime
        o.headimageurl = headimageurl
        o.distance = distance
        o.devicetype = devicetype
        
        o.remark01 = remark01
        o.remark02 = remark02
        o.remark03 = remark03
        
        return o
    }
}

你可能感兴趣的:(Realm 学习笔记 --- RealmSwift)