iOS數(shù)據(jù)持久化之-Realm使用深入詳解篇

原創(chuàng) 2019-11-21

相信關(guān)于Realm的基本使用介紹,在很多文章都已經(jīng)介紹過了,其中訪問比較多的有:
Realm在iOS中的簡單使用
Realm數(shù)據(jù)庫 從入門到“放棄”
官方也講解得比較詳細(xì):官方文檔

在此篇文章中,我只深入的介紹或者說比那些基礎(chǔ)篇更加綜合的驗(yàn)證官方文檔所提及到的關(guān)于【線程】【觀察】【事務(wù)】的操作

首先我們創(chuàng)建兩個(gè)實(shí)例,以此來作為Object講解對象

class Book: Object {
    @objc dynamic var id = 0
    @objc dynamic var name: String?
    let owners = LinkingObjects<Student>(fromType: Student.self, property: "books")

    override static func primaryKey() -> String {
        return "id"
    }
}

class Student: Object {
    @objc dynamic var idCard: String = "20191110"
    @objc dynamic var name: String? = "wang"
    @objc dynamic var age: Int = 0
    let books = List<Book>()

    override static func primaryKey() -> String {
        return "idCard"
    }
}

在進(jìn)一步說明之前,還是先來上一段關(guān)于增刪改查的操作

/// 初始化一個(gè)Realm實(shí)例,將采用默認(rèn)配置
var realm = try! Realm()

/// add 添加/更新一個(gè)對象
let student = Student()
student.idCard = "20191120"
try? realm.write {
    // 當(dāng)update=true時(shí),必須要求Object的subclass實(shí)現(xiàn)override static func primaryKey() -> String
    realm.add(student, update: true)
}
// or
realm.beginWrite()
realm.add(student, update: true)
try? realm.commitWrite()

/// 查詢
let results = realm.objects(Student.self)
print("Basisc   query  results: \(results.count)")

// 還可以進(jìn)行鏈?zhǔn)讲僮?let results0 = results.filter("idCard=%@", "20191120")

// 如果設(shè)置了主鍵還可以查詢指定的對象
let queryStudent = realm.object(ofType: Student.self, forPrimaryKey: "20191120")
print("Basisc  query  object: \(queryStudent)")

/// 刪除一個(gè)對象或者一系列對象
let student1 = Student()
student1.idCard = "20191121"
try? realm.write {
    realm.add(student1, update: true)
}
print("Basisc   delete  before  results count: \(results.count)")
let results1 = realm.objects(Student.self).filter("idCard=%@", "20191121")
try? realm.write {
    realm.delete(results1)
}
print("Basisc   delete  after  results count: \(results.count)")   // 可以看出results結(jié)果實(shí)現(xiàn)了自更新

// 當(dāng)然你也可以
//try? realm.write {
//    realm.delete(student1)
//}

/// 多對多的使用,多對一或一對一
/// 請看Student的:books屬性
let book = Book()
book.id = 1
book.name = "語文"
let book1 = Book()
book1.id = 2
book.name = "數(shù)學(xué)"
try? realm.write {
    realm.add(book, update: true)
    realm.add(book1, update: true)
    student.books.append(book)
    student.books.append(book1)
}
print("Basisc   Many-to-Many  Many-to-One:   \(student.books)")

/// inverse 關(guān)系,什么時(shí)候我們會用到這種反轉(zhuǎn)的關(guān)系呢?當(dāng)一個(gè)東西擁有多個(gè)關(guān)聯(lián)持有者是,我們希望知道他的持有者是誰,那么這個(gè)時(shí)候就有必要
/// 請看Book的:owners 屬性
DispatchQueue.global().async {
    let book = (try! Realm()).object(ofType: Book.self, forPrimaryKey: "1")
    print("Basisc   Many-to-Many  Many-to-One  inverse:   \(book.owners.first)")
}

通過上訴總結(jié)了如下幾點(diǎn):

1、在進(jìn)行任何已被管理的對象操作時(shí)都必須滿足 [Object].isInvalidated == false
2、相同線程下的不同Realm實(shí)例事務(wù)操作不能夠嵌套,因?yàn)檫@個(gè)時(shí)候即使新建Realm實(shí)例,但其還是處于transition中,不同線程下的不同Realm實(shí)例是可以嵌套的
3、Realm不支持自增key
4、Realm查詢到Results僅當(dāng)真正訪問的時(shí)候才會加載到內(nèi)存當(dāng)中,故Realm查詢并不支持limit,并且對數(shù)據(jù)加載到內(nèi)存更友好
5、Realm查詢結(jié)果是自更新的,亦即意味著在任意線程更新了數(shù)據(jù),那么都將會自動(dòng)更新到查詢結(jié)果

這是基本上用到Realm數(shù)據(jù)庫需要了解到的基本

接下來說說在跨線程中的操作及結(jié)果驗(yàn)證

  • Realm實(shí)例對象是否可以跨線程訪問?答案否!
let realm = try! Realm()
print("Thread  isMain: \(Thread.isMainThread)")

/// 這里我們驗(yàn)證一個(gè)打開的realm是否可以跨線程
/// 錯(cuò)誤示例,Realm實(shí)例不能夠跨線程
DispatchQueue.global().async {
    let student = Student()
    try? realm.write {
        realm.add(student, update: true)
   }
}
  • Object子類是否可以跨線程訪問?答案否!
let student = Student()
try? realm.write {
    realm.add(student, update: true)
}
/// 錯(cuò)誤示例,已經(jīng)被Realm示例managed的對象不能夠跨線程
/// 但是處于unmanaged的對象就可以當(dāng)成一般的對象使用,是可以跨線程訪問的,可以嘗試將上述add屏蔽掉再來看看結(jié)果
DispatchQueue.global().async {
    let realm = try! Realm()
    try? realm.write {
        realm.add(student, update: true)
    }
}
  • Results 查詢結(jié)果是否可以跨線程訪問?答案否!
let results = realm.objects(Student.self)
DispatchQueue.global().async {
    print("Thread   Results  access  before")
    print("Thread   Results  access  \(results.count)")   // 這里會出錯(cuò)
    print("Thread   Results  access  after")
}

所以【注意且重要】 Realm、Object、Results 或者 List 受管理實(shí)例皆受到線程的限制,這意味著它們只能夠在被創(chuàng)建的線程上使用,否則就會拋出異常。這是 Realm 強(qiáng)制事務(wù)版本隔離的一種方法。否則,在不同事務(wù)版本中的線程間,通過潛在泛關(guān)系圖 (potentially extensive relationship graph) 來確定何時(shí)傳遞對象將不可能實(shí)現(xiàn)。

那么綜上我們是否就沒有辦法跨線程訪問Realm實(shí)例對象了呢?當(dāng)然不是Realm給我們提供了一個(gè)ThreadSafeReference,來方便我們跨線程訪問

  • 針對Object跨線程訪問
let studentRef = ThreadSafeReference<Student>(to: student)
DispatchQueue.global().async {
    let realm = try! Realm()
    guard let studentCopy = realm.resolve(studentRef) else {
        return
    }
    print("Thread   Object  ThreadSafeReference:  \(studentCopy.idCard)")  // 可以看到結(jié)果正常輸出
}
  • 針對Results跨線程訪問
let resultsRef = ThreadSafeReference<Results<Student>>(to: results)
DispatchQueue.global().async {
    let realm = try! Realm()
    guard let resultsCopy = realm.resolve(resultsRef) else {
        return
    }
    print("Thread   Results  ThreadSafeReference:  \(resultsCopy.count)")  // 可以看到結(jié)果正常輸出

    // 那么我們是否可以進(jìn)一步訪問resultsCopy中的結(jié)果呢? 從輸出結(jié)果看,答案是可以的
    print("Thread   Results  ThreadSafeReference  Object  update before: \(resultsCopy.first)")

    try? realm.write {
        resultsCopy.first?.name = "ThreadSafeReference"
    }
    print("Thread   Results  ThreadSafeReference  Object   update after: \(resultsCopy.first)")
}

當(dāng)然你也可以驗(yàn)證其他List、LinkingObjects,在我的Demo中已經(jīng)提供了這些的所有驗(yàn)證,在文末我將會貼上Demo地址

關(guān)于Realm線程方面的總結(jié)如下:

1、Realm、Object、Results 或者 List 被管理實(shí)例皆受到線程的限制,只能夠在被創(chuàng)建且被管理的實(shí)例線程中使用

2、Object、Results、List也可以通過ThreadSafeReference來跨線程安全訪問,這意味著當(dāng)我們不確定某個(gè)被管理對象或者已經(jīng)確定某個(gè)被管理對象在其他線程使用,開始新線程訪問前我們都都可以通過線程安全引用使其能夠跨線程訪問

3、如果一個(gè)ThreadSafeReference被一個(gè)Realm實(shí)例resolve后,那么ThreadSafeReference所指向的那個(gè)對象的管理Realm也將會變更到當(dāng)前實(shí)例

再來說說Realm數(shù)據(jù)庫的觀察(observe)操作

Realm數(shù)據(jù)庫支持realm實(shí)例全局observe,Results結(jié)果集observe,以及Object對象實(shí)例觀察

  • Realm實(shí)例觀察
let realmToken = realm.observe { (notification, realm) in
    print("Observe   Realm: notification => \(notification),  realm => \(realm)")
}
  • Object實(shí)例觀察
let animal = Animal()
print("Observe   Object is managed  before: \(animal.realm != nil)")
try? realm.write {
    realm.add(animal, update: true)

    let realm0 = try! Realm()
    let animal0 = Animal()
    animal0.id = Int.max
    // 相同線程的不同Realm實(shí)例事務(wù)是不能夠嵌套的
//    try? realm0.write {
//        realm0.add(animal0, update: true)
//    }

    DispatchQueue.global().asyncAfter(deadline: .now(), execute: {
        let animal1 = Animal()
        animal1.id = 1
        let realm1 = try! Realm()

        // 不同的線程Realm實(shí)例事務(wù)是可以嵌套的
        try? realm1.write {
            realm1.add(animal1, update: true)
        }

        /// 注意這里即使在不同的線程不同Realm實(shí)例下,只要在一個(gè)事務(wù)中,都不能夠被觀察
//        _ = animal1.observe({ (change) in
//            print("Observe   Object  在不同的Realm實(shí)例的事務(wù)中觀察")
//        })
    })
}
print("Observe   Object  is managed  after: \(animal.realm != nil)")
// 注意所有的觀察都不能夠在相同Realm實(shí)例的事務(wù)中操作?。。。?// 錯(cuò)誤做法
//try? realm.write {
//    _ = animal.observe({ (_) in
//        // do something
//    })
//}
// 正確做法
let animatToken = animal.observe { (change) in
    switch change {
    case .error(let error):
        print("Observe  Object  error: \(String(describing: error))")
    case .change(let propertyChanges):
        for propertyChange in propertyChanges {
            print("Observe   Object  propertyChange:  name => \(propertyChange.name),  oldValue => \(propertyChange.oldValue),  newValue => \(propertyChange.newValue)")
        }
    default:
        print("Observe   Object  deleted")
    }
}
  • Results結(jié)果集觀察
let resultToken = results.observe { (change: RealmCollectionChange<Results<Animal>>) in
    switch change {
    case .initial(let results):
        print("Observe   Result  initial:   \(results.count)")
        break
    case .update(let results, let deletions, let insertions, let modifications):
        print("Observe   Result  update:   \(results.count)   deletions => \(deletions)  insertions => \(insertions)  modifications => \(modifications)")
    default:
        print("Observe   Result  error")
    }
}

關(guān)于Realm實(shí)例observe方面的總結(jié)如下

1、Realm/Object/Results/List都可被觀察,并且當(dāng)數(shù)據(jù)發(fā)生變化不論是在哪個(gè)進(jìn)程或者線程都會被通知到

2、所有的觀察不可以在Realm的實(shí)例事務(wù)中操作,不管被管理對象是否歸屬于當(dāng)前Realm實(shí)例managed

3、相同線程下的不同Realm實(shí)例事務(wù)操作不能夠嵌套,因?yàn)檫@個(gè)時(shí)候即使新建Realm實(shí)例,但其還是處于transition中,不同線程下的不同Realm實(shí)例是可以嵌套的

更多的詳解及錯(cuò)誤示例,請查看RealmStudy demo

在此篇文章的基礎(chǔ)之上,我還封裝了一個(gè)對外看似以單例操作數(shù)據(jù)庫的manager,使用非常簡單:

// 初始化
XRealm.default.initialize(withUID: "xxx")

// 跨線程寫入
XRealm.default.write {
 // 任何數(shù)據(jù)庫操作
}

// 新增
XRealm.default.add(object, true/false, true)

// 刪除
XRealm.default.delete(object)
XRealm.default.delete(results)
XRealm.default.delete(sequence)

// 查詢
XRealm.default.object(Object.self)

// 事務(wù)中實(shí)現(xiàn)觀察
var token: NotificationToken?
let realm = try! Realm()
let book = Book()
book.id = 0
book.name = "語文"
try? realm.write {
     realm.add(book, update: true)
}
try? realm.write {
    book.name = "數(shù)學(xué)"
    XRealm.default.observe(book, { (change) in
        print("fuck  觀察變更   \(change)")
    }, { [weak self] (token, error) in
        self?.token = token
        print("fuck  觀察  \(token)   \(error)")
    })
}

其處理了:
1、對數(shù)據(jù)增刪改的數(shù)據(jù)是否invalidate的驗(yàn)證
2、事務(wù)嵌套操作的規(guī)避,即如果已經(jīng)開啟了事務(wù)那么將不會在begin一個(gè)事務(wù)
3、關(guān)于如何規(guī)避在事務(wù)中執(zhí)行observe造成的崩潰問題
關(guān)于如何處理的可以查看XRealm源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容