iOS开发

RealmSwift

2019-05-30  本文已影响55人  磊Se

官网链接

简介

优势与亮点

使用cocoaPods安装

使用Realm Studio

使用Realm框架

1、Realm数据库

2、打开Realm数据库

  //默认defalut.realm
  let realm = try! Realm()
  try! realm.write {
    realm.add(dog)
  }

第一次创建Realm实例在资源受限的情况下可能会发生错误,使用swift内置的错误处理机制

  do {
    let realm = try Realm()
  } catch let error as NSError {
      // 错误处理
  }
  var config = Realm.Configuration()
  //设置某些类只能存储到当前realm数据库中
  config.objectTypes = [MyClass.self, MyOtherClass.self]
  // 使用默认的目录,但是请将文件名替换为用户名
  config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(username).realm")
  // 只读
  config.readOnly = true
  // 将该配置设置为默认 Realm 配置 设置后Realm()就是当前Realm对象
  Realm.Configuration.defaultConfiguration = config
  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 数据库期间所出现的错误
      }
  }

3、数据模型

创建数据模型
支持的属性类型
属性特性
class Person: Object {
    // 可空字符串属性,默认为 nil
    @objc dynamic var name: String? = nil
    // 可选 int 属性,默认为 nil
    // RealmOption 属性应该始终用 `let` 进行声明,
    // 因为直接对其进行赋值并不会起任何作用
    let age = RealmOptional<Int>()
    //一般不用可空数据类型,常规写法
    @objc dynamic var num: Int = 0
    let dogs = List<Dog>()
}
关系
class Dog: Object {
    // ... 其余属性声明
    @objc dynamic var owner: Person? // 对一关系必须设置为可空
}

let jam = Person()
let rex = Dog()
rex.owner = jim
class Person: Object {
    // ...其他属性声明
    let dogs = List<Dog>()
}

您可以照常对 List 属性进行访问和赋值:

let someDogs = realm.objects(Dog.self).filter("name contains 'Fido'")
jim.dogs.append(objectsIn: someDogs)
jim.dogs.append(rex)

List属性会确保其内部插入次序不会被打乱
注意,不支持包含原始类型的List进行查询

class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0
    let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}
主键、被忽略属性、索引属性
import RealmSwift

// 狗狗的数据模型
class Dog: Object {
    @objc dynamic var id: Int = 0
    @objc dynamic var owner: Person? // 属性可以设置为可选

    override static func primaryKey() -> String? {
        return "id" //这里id 是模型类声明的名称
    }
    override static func ignoreProperties() -> [String] {
        return ["owner"]
    }
}

4、数据库基本操作

对象的所有更改(添加、修改和删除)都必须在写入事务内完成。

添加数据
  //(1)创建Dog对象
  let dog: Dog = Dog()
  dog.name = "Wang"
  dog.age = 18
  dog.id = 1
  // (2) 从字典中创建 Dog 对象
  let myOtherDog = Dog(value: ["name" : "Pluto", "age": 3,"id":2])
  // (3) 从数组中创建 Dog 对象
  let myThirdDog = Dog(value: ["Fido", 5, 3])
  try! realm!.write {
        realm!.add(dog)  //注意如果添加已有主键对象会崩溃
  //   realm!.add(dog, update: true)//有主键则更新,无则更新
  }
更新数据
  //更新
  //通过主键更新
  let dog: Dog = Dog()
  dog.age = 19
  dog.id = 1
  dog.name = "Lala"
  //当没有当前主键即增
  try! realm!.write {
            realm!.add(dog, update: true)
  }
 //直接更新
  try! realm!.write {
        dog.age = 20
  }
//键值编码
//`Object`、`Result` 和 `List` 均允许使用 键值编码(KVC)
  let persons = realm.objects(Person.self)
  try! realm.write {
      //第一个person对象的isFirst设置为true
      persons.first?.setValue(true, forKeyPath: "isFirst")
      // 将每个 person 对象的 planet 属性设置为 "Earth"
      persons.setValue("Earth", forKeyPath: "planet")
  }
查询数据
//最基本查询方法
let dogs = realm.objects(Dog.self)//从realm数据库查询所有dog对象

//条件查询
// 使用断言字符串来查询
var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")
// 使用 NSPredicate 来查询
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog.self).filter(predicate)

//链式查询:realm它能够用很小的事务开销来实现链式查询
let tanDogs = realm.objects(Dog.self).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")

// 对颜色为棕黄色、名字以 "B" 开头的狗狗进行排序
let sortedDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted(byKeyPath: "name")

请注意,sorted(byKeyPath:)sorted(byProperty:) 不支持 将多个属性用作排序基准,此外也无法链式排序(只有最后一个 sorted 调用会被使用)如果要对多个属性进行排序,请使用 sorted(by:) 方法,然后向其中输入多个 SortDescriptor 对象。

// 循环读取出前 5 个 Dog 对象
// 从而限制从磁盘中读取的对象数量
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
    let dog = dogs[i]
    // ...
}
  let puppies = realm.objects(Dog.self).filter("age < 2")
  puppies.count // => 0
  try! realm.write {
      realm.create(Dog.self, value: ["name": "Fido", "age": 1])
  }
  puppies.count // => 1
删除对象
  // 在事务中删除对象
try! realm.write {
    realm.delete(cheeseBook)
}
// 从 Realm 数据库中删除所有对象
try! realm.write {
    realm.deleteAll()
}

数据迁移(更新数据库)

假设原有模型类

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

添加fullname属性,去掉firstName和lastName

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

本地迁移

通过设置Realm.Configuration.schemaVersion 以及 Realm.Configuration.migrationBlock可以定义本地迁移。

  // 此段代码位于 application(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.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
                // 将两个 name 合并到 fullName 当中
                let firstName = oldObject!["firstName"] as! String
                let lastName = oldObject!["lastName"] as! String
                newObject!["fullName"] = "\(firstName) \(lastName)"
            }
        }
    })

还有属性重命名、线性迁移

通知

Realm通知

当整个 Realm 数据库发生变化时,就会发送通知

// 注册 Realm 通知
let token = realm.observe { notification, realm in
    viewController.updateUI()
}

// 随后
token.invalidate()
集合通知

通过传递到通知模块当中的 RealmCollectionChange 参数来访问这些变更。该对象存放了受删除 (deletions)、插入 (insertions) 以及修改 (modifications) 所影响的索引信息

  class ViewController: UITableViewController {
    var notificationToken: NotificationToken? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        let realm = try! Realm()
        let results = realm.objects(Person.self).filter("age > 5")

        // 订阅 Results 通知
        notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
            guard let tableView = self?.tableView else { return }
            switch changes {
            case .initial:
                // Results 现在已经填充完数据,无需阻塞 UI 便可直接访问
                tableView.reloadData()
            case .update(_, let deletions, let insertions, let modifications):
                // 检索结果发生改变,将其应用到 UITableView
                tableView.beginUpdates()
                tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
                                     with: .automatic)
                tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
                                     with: .automatic)
                tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
                                     with: .automatic)
                tableView.endUpdates()
            case .error(let error):
                // 在后台工作线程中打开 Realm 文件发生了错误
                fatalError("\(error)")
            }
        }
    }

    deinit {
        notificationToken?.invalidate()
    }
}
对象通知

您可以在特定的 Realm 对象上进行通知的注册,这样就可以在此对象被删除时、或者该对象所管理的属性值被修改时,获取相应的通知。

  class StepCounter: Object {
    @objc dynamic var steps = 0
}

let stepCounter = StepCounter()
let realm = try! Realm()
try! realm.write {
    realm.add(stepCounter)
}
var token : NotificationToken?
token = stepCounter.observe { change in
    switch change {
    case .change(let properties):
        for property in properties {
            if property.name == "steps" && property.newValue as! Int > 1000 {
                print("Congratulations, you've exceeded 1000 steps.")
                token = nil
            }
        }
    case .error(let error):
        print("An error occurred: \(error)")
    case .deleted:
        print("The object was deleted.")
    }
}
界面驱动更新
  // 添加细粒化通知模块
token = collection.observe { changes in
    switch changes {
    case .initial:
        tableView.reloadData()
    case .update(_, let deletions, let insertions, let modifications):
        // 检索结果发生改变,将其应用到 UITableView
        tableView.beginUpdates()
        tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
                             with: .automatic)
        tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
                             with: .automatic)
        tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
                             with: .automatic)
        tableView.endUpdates()
    case .error(let error):
        // 处理错误
        ()
    }
}

func insertItem() throws {
     // 在主线程执行界面驱动更新:
     collection.realm!.beginWrite()
     collection.insert(Item(), at: 0)
     // 随后立即将其同步到 UI 当中
     tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
     // 确保变更通知不会再次响应变更
     try collection.realm!.commitWrite(withoutNotifying: [token])
}
键值观察

加密

  // 生成随机秘钥
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
    SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}

// 打开已加密的 Realm 文件
let config = Realm.Configuration(encryptionKey: key)
do {
    let realm = try Realm(configuration: config)
    // 照常使用 Realm 数据库
    let dogs = realm.objects(Dog.self).filter("name contains 'Fido'")
} catch let error as NSError {
    // 如果秘钥错误,`error` 会提示数据库无法访问
    fatalError("Error opening realm: \(error)")
}

线程

检视其他线程上的变化

跨线程传递实例
跨线程使用 Realm 数据库

JSON

Realm 没有提供对 JSON 的直接支持,但是您可以使用 NSJSONSerialization.JSONObjectWithData(_:options:) 的输出,实现将 JSON 添加到 Object 的操作。

上一篇下一篇

猜你喜欢

热点阅读