iOS学习

IOS DB存储之Realm.swift (二) 使用详解

2019-10-17  本文已影响0人  孔雨露

@[TOC](IOS DB存储之Realm.swift 使用详解)

1. Realm简介

1.1 什么是Realm

Realm是一个跨平台的移动数据库引擎,于2014 年7月发布,是一个跨平台的移动数据库引擎,专门为移动应用的数据持久化而生。其目的是要取代 Core Data 和 SQLite。目前支持iOS、Android平台,同时支持Objective-C、Swift、Java、React Native、Xamarin等多种编程语言* Realm并不是对SQLite或者CoreData的简单封装, 是由核心数据引擎C++打造,是拥有独立的数据库存储引擎,可以方便、高效的完成数据库的各种操作.

Realm官网
Realm官方文档
Realm GitHub

1.2 Realm的优点

  1. 跨平台:现在很多应用都是要兼顾iOS和Android两个平台同时开发。如果两个平台都能使用相同的数据库,那就不用考虑内部数据的架构不同,使用Realm提供的API,可以使数据持久化层在两个平台上无差异化的转换。
  2. 简单易用:Core Data 和 SQLite 冗余、繁杂的知识和代码足以吓退绝大多数刚入门的开发者,而换用 Realm,则可以极大地减少学习成本,立即学会本地化存储的方法。毫不吹嘘的说,把官方最新文档完整看一遍,就完全可以上手开发了。
  3. 可视化Realm 还提供了一个轻量级的数据库查看工具,在Mac Appstore 可以下载“Realm Browser”这个工具,开发者可以查看数据库当中的内容,执行简单的插入和删除数据的操作。毕竟,很多时候,开发者使用数据库的理由是因为要提供一些所谓的“知识库”。

1.3 Realm支持的类型

  1. Realm 支持以下的属性类型:Bool、Int8、Int16、Int32、Int64、Double、Float、String、Date(精度到秒)以及Data.
  2. 也可以使用 List<object>Object 来建立诸如一对多、一对一之类的关系模型,此外 Object 的子类也支持此功能。

2. Realm安装配置

2.1 Cocospod安装

pod 'RealmSwift'

2.2 手动集成

(1)先去 Realm 的官网去下载最新框架:http://static.realm.io/downloads/swift/latest
(2)拖拽 RealmSwift.framework 和 Realm.framework 文件到”Embedded Binaries”选项中。选中 Copy items if needed 并点击 Finish

2.3 配置 Realm

2.3.1 配置 Realm 数据库

  1. 对于本地 Realm 数据库而言,可以配置 Realm 文件在磁盘上的路径;
  2. 对于可同步 Realm 数据库而言,可以配置管理该 Realm 数据库的用户,以及 Realm 数据库在 Realm 对象服务器上的远程路径;
  3. 对于架构版本之间发生变化的 Realm 数据库而言,可以通过迁移功能来控制旧架构的 Realm 数据该如何更新到最新的架构。
  4. 对于存储的数据量过大、或者数据频繁发生变化的 Realm 数据库而言,可以通过压缩功能来控制 Realm 文件该如何实现压缩,从而确保能高效地利用磁盘空间。
func setDefaultRealmForUser(username: String) {
    var config = Realm.Configuration()
    // 使用默认的目录,但是请将文件名替换为用户名
    config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(username).realm")
    // 将该配置设置为默认 Realm 配置
    Realm.Configuration.defaultConfiguration = config
}

2.3.1.1 打包进项目里的数据库的使用

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")

2.3.2 同步打开 Realm 数据库

// 创建配置
let syncServerURL = URL(string: "realm://localhost:9080/~/userRealm")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))

// 打开远程 Realm 数据库
let realm = try! Realm(configuration: config)
// 任何对此 Realm 数据库所做的操作,都会同步到所有设备上!

2.3.3 异步打开 Realm 数据库

2.3.4 内存中 Realm 数据库

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

2.3.5 删除 Realm 文件

autoreleasepool {
    // 在这里进行所有的 Realm 操作
}
let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
let realmURLs = [
    realmURL,
    realmURL.appendingPathExtension("lock"),
    realmURL.appendingPathExtension("note"),
    realmURL.appendingPathExtension("management")
]
for URL in realmURLs {
    do {
        try FileManager.default.removeItem(at: URL)
    } catch {
        // 错误处理
    }
}

3. Realm数据迁移

3.1 为何要迁移

  1. 比如原来有如下 Person 模型:
class Person: Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var age = 0
}
  1. 假如我们想要更新数据模型,给它添加一个 fullname 属性, 而不是将“姓”和“名”分离开来。
class Person: Object {
    @objc dynamic var fullName = ""
    @objc dynamic var age = 0
}
  1. 在这个时候如果您在数据模型更新之前就已经保存了数据的话,那么 Realm 就会注意到代码和硬盘上数据不匹配。 每当这时,您必须进行数据迁移,否则当您试图打开这个文件的话 Realm 就会抛出错误。

3.2 如何进行数据迁移

3.2.1 修改数据表结构

// 在(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()
// 在 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)"
      }
    }
  })

3.2.2 重命名表字段

// Inside your application(application:didFinishLaunchingWithOptions:)

Realm.Configuration.defaultConfiguration = Realm.Configuration(
    schemaVersion: 1,
    migrationBlock: { migration, oldSchemaVersion in
        // We haven’t migrated anything yet, so oldSchemaVersion == 0
        if (oldSchemaVersion < 1) {
            // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`.
            migration.renameProperty(onType: Person.className(), from: "yearsSinceBirth", to: "age")
        }
    })

4. Realm加密

(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")

5. Realm基本使用

5.1 增,删,改,查操作

5.1.1 新增

let realm = try! Realm()
try! realm.write {
    realm.add(myDog)
}

注意:Realm 的写入操作是同步以及阻塞进行的,它并不会异步执行。如果线程 A 开始进行写入操作,然后线程 B 在线程 A 结束之前,对相同的 Realm 数据库也执行了写入操作,那么线程 A 必须要在线程 B 的写入操作发生之前,结束并提交其事务。写入事务会在 beginWrite() 执行时自动刷新,因此重复写入并不会产生竞争条件。

let myDog = Dog()
myDog.name = "Fido"
myDog.age = 1
try! realm.write {
    realm.add(myDog)
}
let myPuppy = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
    myPuppy!.age = 2
}
print("age of my dog: \(myDog.age)") // => 2

5.1.1.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?
}
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()
    }
}

5.1.2 更新

5.1.2.1 直接更新

// 在事务中更新对象
try! realm.write {
    author.name = "Thomas Pynchon"
}

5.1.2.2 键值编码更新

let persons = realm.objects(Person.self)
try! realm.write {
    persons.first?.setValue(true, forKeyPath: "isFirst")
    // 将每个 person 对象的 planet 属性设置为 "Earth"
    persons.setValue("Earth", forKeyPath: "planet")
}

5.1.2.3 通过主键更新

// 创建一个 book 对象,其主键与之前存储的 book 对象相同
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1
// 更新这个 id = 1 的 book
try! realm.write {
    realm.add(cheeseBook, update: true)
}
// 假设主键为 `1` 的 "Book" 对象已经存在
try! realm.write {
    realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
    // book 对象的 `title` 属性仍旧保持不变
}

5.1.3 删除

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

5.1.2 查询

5.1.2.1 简单查询

let dogs = realm.objects(Dog.self) // 从默认的 Realm 数据库中遍历所有 Dog 对象

5.1.2.2 条件查询

// 使用断言字符串来查询
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)

5.1.2.3 查询排序

// 对颜色为棕黄色、名字以 "B" 开头的狗狗进行排序
let sortedDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted(byKeyPath: "name")
class Person: Object {
    @objc dynamic var name = ""
    @objc dynamic var dog: Dog?
}
class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0
}

let dogOwners = realm.objects(Person.self)
let ownersByDogAge = dogOwners.sorted(byKeyPath: "dog.age")

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

5.1.2.4 链式查询

let tanDogs = realm.objects(Dog.self).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")

5.1.2.5 查询结果的自更新

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 {
    for person in realm.objects(Person.self).filter("age == 10") {
        person.age += 1
    }
}

5.1.2.6 限制查询结果


// 循环读取出前 5 个 Dog 对象
// 从而限制从磁盘中读取的对象数量
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
    let dog = dogs[i]
    // ...
}

5.2 模型定义

5.2.1 属性的Setter 和 Getter

5.2.2 自动增长属性

5.2.3 计算型属性

5.2.4 忽略属性(不会映射到DB)

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"]
    }
}

5.2.5 添加主键(Primary Keys)

class Person: Object {
    @objc dynamic var id = 0
    @objc dynamic var name = ""
     
    override static func primaryKey() -> String? {
        return "id"
    }
}

5.2.6 添加索引属性(Indexed Properties)

class Book: Object {
    @objc dynamic var price = 0
    @objc dynamic var title = ""
     
    override static func indexedProperties() -> [String] {
        return ["title"]
    }
}

5.2.7 反向关系(Inverse Relationship)

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")
}

5.2.8 使用List实现一对多关系

class Person: Object {
    ... // 其余的属性声明
    let dogs = List<Dog>()
}
 
// 这里我们就可以使用已存在的狗狗对象来完成初始化
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])
 
// 还可以使用多重嵌套
let aPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺财", 6]]])
let someDogs = realm.objects(Dog.self).filter("name contains '小白'")
ZhangSan.dogs.append(objectsIn: someDogs)
ZhangSan.dogs.append(dahuang)

5.2.9 Objective-C 中的属性

5.2.10 Object 子类的自定义构造器

class MyModel: Object {
    @objc dynamic var myValue = ""
    convenience init(myValue: String) {
        self.init() // 请注意这里使用的是 'self' 而不是 'super'
        self.myValue = myValue
    }
}

5.3 通知处理

5.3.1 Realm 通知

// 获取 Realm 通知
let token = realm.observe { notification, realm in
    viewController.updateUI()
}
// 随后
token.invalidate()

5.3.2 集合通知

5.3.3 对象通知

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.")
    }
}

5.4 JSON 数据处理

5.4.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": ""
        }
    ]
}
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"
    }
}
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")!
        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 ?? "")
    }
     
}

6. Realm使用注意

6.1 对字符长度的限制

  1. 类名称的长度最大只能存储 57 个 UTF8 字符。
  2. 属性名称的长度最大只能支持 63 个 UTF8 字符。
  3. Data 和 String 属性不能保存超过 16 MB 大小的数据。如果要存储大量的数据,可通过将其分解为16MB 大小的块,或者直接存储在文件系统中,然后将文件路径存储在 Realm 中。如果您的应用试图存储一个大于 16MB 的单一属性,系统将在运行时抛出异常。
  4. 对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“(UTF-8 的范围在 0~591 之间)。
  5. 每个单独的 Realm 文件大小无法超过应用在 iOS 系统中所被允许使用的内存量——这个量对于每个设备而言都是不同的,并且还取决于当时内存空间的碎片化情况(关于此问题有一个相关的 Radar:rdar://17119975)。如果您需要存储海量数据的话,那么可以选择使用多个 Realm 文件并进行映射。

6.2 Realm使用注意点

6.2.1 多线程问题

6.2.1.1 跨线程使用 Realm 数据库
  1. 在不同的线程中使用同一个 Realm 文件,必须每一个线程初始化一个新的Realm 实例。
  2. 不支持跨线程共享Realm 实例。Realm 实例要访问相同的 Realm 文件还必须使用相同的 Realm.Configuration
6.2.1.1 跨线程访问数据库,Realm对象一定需要新建一个
*** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'******* First throw call stack:****(**** 0   CoreFoundation                      0x000000011479f34b __exceptionPreprocess + 171**** 1   libobjc.A.dylib                     0x00000001164

如果程序崩溃了,出现以上错误,那就是因为你访问Realm数据的时候,使用的Realm对象所在的线程和当前线程不一致。

6.2.1.2 .查询也不能跨线程查询
RLMResults * results = [self selectUserWithAccid:bhUser.accid];    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm transactionWithBlock:^{
            [realm addOrUpdateObject:results[0]];
        }];
    });
***** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread'******* First throw call stack:****(**** 0   CoreFoundation                      0x000000011517a34b __exceptionPreprocess + 171**** 1   libobjc.A.dylib                     0x0000000116

6.2.2 Realm对象的 Setters & Getters 不能被重载

6.2.3 文件大小 & 版本跟踪

一般来说 Realm 数据库比 SQLite 数据库在硬盘上占用的空间更少。如果您的 Realm 文件大小超出了您的想象,这可能是因为您数据库中的 RLMRealm中包含了旧版本数据。
为了使您的数据有相同的显示方式,Realm 只在循环迭代开始的时候才更新数据版本。这意味着,如果您从 Realm 读取了一些数据并进行了在一个锁定的线程中进行长时间的运行,然后在其他线程进行读写 Realm 数据库的话,那么版本将不会被更新,Realm 将保存中间版本的数据,但是这些数据已经没有用了,这导致了文件大小的增长。这部分空间会在下次写入操作时被重复利用。这些操作可以通过调用writeCopyToPath:error:来实现。

通过调用invalidate,来告诉 Realm 您不再需要那些拷贝到 Realm 的数据了。这可以使我们不必跟踪这些对象的中间版本。在下次出现新版本时,再进行版本更新。
您可能在 Realm 使用Grand Central Dispatch时也发现了这个问题。在 dispatch 结束后自动释放调度队列(dispatch queue)时,调度队列(dispatch queue)没有随着程序释放。这造成了直到
RLMRealm 对象被释放后,Realm 中间版本的数据空间才会被再利用。为了避免这个问题,您应该在 dispatch 队列中,使用一个显式的自动调度队列(dispatch queue)。

6.2.4 Realm 没有自动增长属性

Realm 没有线程/进程安全的自动增长属性机制,这在其他数据库中常常用来产生主键。然而,在绝大多数情况下,对于主键来说,我们需要的是一个唯一的、自动生成的值,因此没有必要使用顺序的、连续的、整数的 ID 作为主键。

在这种情况下,一个独一无二的字符串主键通常就能满足需求了。一个常见的模式是将默认的属性值设置为 [[NSUUID UUID] UUIDString]
以产生一个唯一的字符串 ID。
自动增长属性另一种常见的动机是为了维持插入之后的顺序。在某些情况下,这可以通过向某个 RLMArray中添加对象,或者使用 [NSDate date]默认值的createdAt属性。

6.2.5 所有的数据模型必须直接继承自RealmObject

Realm 允许模型能够生成更多的子类,也允许跨模型进行代码复用,但是由于某些 Cocoa 特性使得运行时中丰富的类多态无法使用。以下是可以完成的操作:

父类中的类方法,实例方法和属性可以被它的子类所继承

子类中可以在方法以及函数中使用父类作为参数

以下是不能完成的:

多态类之间的转换(例如子类转换成子类,子类转换成父类,父类转换成子类等)

同时对多个类进行检索

多类容器 (RLMArray以及 RLMResults)

6.2.6 Realm不支持集合类型

这一点也是比较蛋疼。

Realm支持以下的属性类型:BOOL、bool、int、NSInteger、long、long long、float、double、NSString、NSDate、NSData以及 被特殊类型标记的NSNumber。CGFloat属性的支持被取消了,因为它不具备平台独立性。

这里就是不支持集合,比如说NSArray,NSMutableArray,NSDictionary,NSMutableDictionary,NSSet,NSMutableSet。如果服务器传来的一个字典,key是一个字符串,对应的value就是一个数组,这时候就想存储这个数组就比较困难了。当然Realm里面是有集合的,就是RLMArray,这里面装的都是RLMObject。

所以我们想解决这个问题,就需要把数据里面的东西都取出来,如果是model,就先自己接收一下,然后转换成RLMObject的model,再存储到RLMArray里面去,这样转换一遍,还是可以的做到的。

6.2.7 建议每个model都需要设置主键,这样可以方便add和update

如果能设置主键,请尽量设置主键,因为这样方便我们更新数据,我们可以很方便的调用addOrUpdateObject: 或者 createOrUpdateInRealm:withValue:方法进行更新。这样就不需要先根据主键,查询出数据,然后再去更新。有了主键以后,这两步操作可以一步完成。

6.2.8 事务处理注意点

6.2.8.1 transactionWithBlock 已经处于一个写的事务中,事务之间不能嵌套
[realm transactionWithBlock:^{
                [self.realm beginWriteTransaction];
                [self convertToRLMUserWith:bhUser To:[self convertToRLMUserWith:bhUser To:nil]];
                [self.realm commitWriteTransaction];
            }];

出错信息如下:

*** Terminating app due to uncaught exception 'RLMException', reason: 'The Realm is already in a write transaction'******* First throw call stack:****(**** 0   CoreFoundation                      0x0000000112e2d34b __exceptionPreprocess + 171**** 1   libobjc.A.dylib                     0x00000001

6.2.9 自己封装一个Realm全局实例单例是没啥作用的

上一篇下一篇

猜你喜欢

热点阅读