[Realm-Swift] 數(shù)據(jù)庫的使用詳解

swift開發(fā)需要應(yīng)用數(shù)據(jù)庫,fmdb筆記繁瑣,在swift中準(zhǔn)備放棄使用,看到了 Realm這個三方框架很強大,而且使用簡單,就拿過來研究一下,感謝Swift - Realm數(shù)據(jù)庫的使用詳解(附樣例) 這篇文章,結(jié)合Realm官網(wǎng)知識Realm官網(wǎng)知識及GitHub的demoRealmCooa,可以熟練使用,以此做個記錄,以防下次遺忘

1、什么是RealmRealm

于2014 年7月發(fā)布,是一個跨平臺的移動數(shù)據(jù)庫引擎,專門為移動應(yīng)用的數(shù)據(jù)持久化而生。其目的是要取代 Core Data 和 SQLite。

2、關(guān)于Realm,你要知道下面幾點:

(1)使用簡單,大部分常用的功能(比如插入、查詢等)都可以用一行簡單的代碼輕松完成,學(xué)習(xí)成本低。
(2)Realm 不是基于 Core Data,也不是基于 SQLite 封裝構(gòu)建的。它有自己的數(shù)據(jù)庫存儲引擎。
(3)Realm 具有良好的跨平臺特性,可以在 iOS 和 Android 平臺上共同使用。代碼可以使用 Swift 、 Objective-C 以及 Java 語言來編寫。
(4)Realm 還提供了一個輕量級的數(shù)據(jù)庫查看工具(Realm Browser)。你也可以用它進(jìn)行一些簡單的編輯操作(比如插入和刪除操作)

3、支持的類型

(1)Realm 支持以下的屬性類型:Bool、Int8、Int16、Int32、Int64、Double、Float、String、Date(精度到秒)以及Data.
(2)也可以使用 List<object> 和 Object 來建立諸如一對多、一對一之類的關(guān)系模型,此外 Object 的子類也支持此功能。

4、Realm的安裝配置

(1)先去 Realm 的官網(wǎng)去下載最新框架:http://static.realm.io/downloads/swift/latest
(2)拖拽 RealmSwift.framework 和 Realm.framework 文件到”Embedded Binaries”選項中。選中 Copy items if needed 并點擊 Finish

Realm-Swift數(shù)據(jù)庫的使用詳解

5、將數(shù)據(jù)插入到數(shù)據(jù)庫中

下面代碼判斷默認(rèn)數(shù)據(jù)庫中是否有數(shù)據(jù),如果沒有的話將幾個自定義對像插入到數(shù)據(jù)庫中。
(1)這里以個人消費記錄為例,我們先定義消費類別類,和具體消費記錄類

import Foundation
import RealmSwift
 
//消費類型
class ConsumeType:Object {
    //類型名
    dynamic var name = ""
}
 
//消費條目
class ConsumeItem:Object {
    //條目名
    dynamic var name = ""
    //金額
    dynamic var cost = 0.00
    //時間
    dynamic var date = Date()
    //所屬消費類別
    dynamic var type:ConsumeType?
}

(2)判斷數(shù)據(jù)庫記錄是否為空,空的話則插入數(shù)據(jù)庫(這里以默認(rèn)數(shù)據(jù)庫為例)

import UIKit
import RealmSwift
 
class ViewController: UIViewController {
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        //使用默認(rèn)的數(shù)據(jù)庫
        let realm = try! Realm()
        //查詢所有的消費記錄
        let items = realm.objects(ConsumeItem.self)
        //已經(jīng)有記錄的話就不插入了
        if items.count>0 {
            return
        }
         
        //創(chuàng)建兩個消費類型
        let type1 = ConsumeType()
        type1.name = "購物"
        let type2 = ConsumeType()
        type2.name = "娛樂"
         
        //創(chuàng)建三個消費記錄
        let item1 = ConsumeItem(value: ["買一臺電腦",5999.00,Date(),type1]) //可使用數(shù)組創(chuàng)建
         
        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
         
        // 數(shù)據(jù)持久化操作(類型記錄也會自動添加的)
        try! realm.write {
            realm.add(item1)
            realm.add(item2)
            realm.add(item3)
        }
         
        //打印出數(shù)據(jù)庫地址
        print(realm.configuration.fileURL)
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

6、Data類型數(shù)據(jù)的存取

1.實現(xiàn)原理
(1)Realm 支持 Data 類型的屬性,我們要做的就是將圖片轉(zhuǎn)換為 Data 類型,再進(jìn)行存儲即可。
(2)Data 類型的屬性讀取操作同其他數(shù)據(jù)類型的讀取沒什么差別。只要注意不要超過 16MB 即可。
2.實現(xiàn)
(1)點擊“保存”按鈕,將項目中的 abc.png 這張圖片存儲到 Realm 數(shù)據(jù)庫中。
(2)點擊“讀取”按鈕,從庫中取出最新保存的那張圖片,并顯示在 imageview 中。
3.樣例代碼

import UIKit
import RealmSwift
 
class ViewController: UIViewController {
    //用于顯示圖片
    @IBOutlet weak var imageView: UIImageView!
     
    //使用默認(rèn)的數(shù)據(jù)庫
    let realm = try! Realm()
     
    override func viewDidLoad() {
        super.viewDidLoad()
    }
     
    //點擊保存
    @IBAction func saveData(_ sender: Any) {
        //獲取圖片并轉(zhuǎn)換為Data
        let imageURL =  Bundle.main.url(forResource: "0", withExtension: "png")!
        let imageData = try! Data(contentsOf: imageURL)
        //將Data數(shù)據(jù)放到實體對象中
        let portrait = HeadPortrait()
        portrait.data = imageData
        //數(shù)據(jù)持久化操作(類型記錄也會自動添加的)
        try! realm.write {
            realm.add(portrait)
        }
        print("數(shù)據(jù)保存完畢!")
    }
     
    //點擊加載
    @IBAction func loadData(_ sender: Any) {
        //獲取所有頭像圖片(根據(jù)插入時間倒序排列)
        let portraits = realm.objects(HeadPortrait.self).sorted(byKeyPath: "date", ascending: false)
        //將最新一張圖片顯示出來
        if portraits.count > 0 {
            if let imgData = portraits[0].data {
                self.imageView.image = UIImage(data: imgData)
            }
        }
         print("數(shù)據(jù)讀取完畢!")
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
 
//用戶頭像
class HeadPortrait:Object {
    //圖片數(shù)據(jù)
    dynamic var data:Data?
     
    //創(chuàng)建時間
    dynamic var date = Date()
}

7、使用Realm Browser查看數(shù)據(jù)庫

(1)默認(rèn)數(shù)據(jù)庫是應(yīng)用的 Documents 文件夾下的一個名為“default.realm”。

//打印出數(shù)據(jù)庫地址

print(realm.configuration.fileURL)

(2)使用 Realm Browser 工具可以很方便的對.realm數(shù)據(jù)庫進(jìn)行讀取和編輯(在 App Store 中搜索 Realm Browser 即可下載)。
可以看到,上面的幾個對象已經(jīng)成功的插入到數(shù)據(jù)庫中來。

8、從數(shù)據(jù)庫中讀取記錄并顯示到表格中來

(1)通過查詢操作,Realm 將會返回包含 Object 集合的 Results 實例。Results 的表現(xiàn)和 Array 十分相似,并且包含在 Results 中的對象能夠通過索引下標(biāo)進(jìn)行訪問。
(2)所有的查詢(包括查詢和屬性訪問)在 Realm 中都是延遲加載的,只有當(dāng)屬性被訪問時,才能夠讀取相應(yīng)的數(shù)據(jù)。
(3)查詢結(jié)果并不是數(shù)據(jù)的拷貝:修改查詢結(jié)果(在寫入事務(wù)中)會直接修改硬盤上的數(shù)據(jù)。
下面我們把庫里的數(shù)據(jù)加載出來,并通過表格顯示出來。
效果圖如下:

代碼如下:

import UIKit
import RealmSwift
 
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource  {
     
    @IBOutlet weak var tableView: UITableView!
     
    var dformatter = DateFormatter()
     
    //保存從數(shù)據(jù)庫中查詢出來的結(jié)果集
    var consumeItems:Results<ConsumeItem>?
     
    override func viewDidLoad() {
        super.viewDidLoad()
         
        self.dformatter.dateFormat = "MM月dd日 HH:mm"
         
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //創(chuàng)建一個重用的單元格
        self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
         
        //使用默認(rèn)的數(shù)據(jù)庫
        let realm = try! Realm()
        //查詢所有的消費記錄
        consumeItems = realm.objects(ConsumeItem.self)
    }
     
    //在本例中,只有一個分區(qū)
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1;
    }
     
    //返回表格行數(shù)(也就是返回控件數(shù))
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.consumeItems!.count
    }
     
    //創(chuàng)建各單元顯示內(nèi)容(創(chuàng)建參數(shù)indexPath指定的單元)
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        //同一形式的單元格重復(fù)使用,在聲明時已注冊
        let cell = UITableViewCell(style: .value1, reuseIdentifier: "MyCell")
        let item = self.consumeItems![indexPath.row]
        cell.textLabel?.text = item.name + " ¥" + String(format: "%.1f", item.cost)
        cell.detailTextLabel?.text = self.dformatter.string(from: item.date)
        return cell
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

9、查詢前N條數(shù)據(jù)Realm無法直接限制查詢數(shù)量。所以我們?nèi)绻胍槌霾糠謹(jǐn)?shù)據(jù)(比如前5條記錄),也是全部查出來后在結(jié)果集中撈取。

//查詢并取出前5條數(shù)據(jù)
let dogs = try! Realm().objects(Dog.self)
for i in 0..<5 {
    let dog = dogs[i]
    // ...
}

Realm為何無法限制查詢數(shù)量?通常查詢數(shù)據(jù)庫數(shù)據(jù)時,我們可以在sql語句中添加一些限制語句(比如rownum,limit,top等)來限制返回的結(jié)果集的行數(shù)。但我們使用Realm會發(fā)現(xiàn),它沒有這種分頁功能,感覺不管查什么都是把所有的結(jié)果都撈出來。比如我們只要User表的前10條數(shù)據(jù),那么做法是先查詢出所有的User數(shù)據(jù),再從結(jié)果集中取出前10條數(shù)據(jù)。有人可能會擔(dān)心,如果數(shù)據(jù)庫中數(shù)據(jù)非常多,那每次都這么查不會影響性能嗎?其實大可放心,由于Realm都是延遲加載的,只有當(dāng)屬性被訪問時,才能夠讀取相應(yīng)的數(shù)據(jù)。不像通常數(shù)據(jù)庫,查詢后,查詢結(jié)果是從數(shù)據(jù)庫拷貝一份出來放在內(nèi)存中的。而Realm的查詢結(jié)果應(yīng)該說是數(shù)據(jù)庫數(shù)據(jù)的引用,就算你查出來,如果不用也不會占用什么內(nèi)存。

10、支持?jǐn)嘌圆樵?Predicate),這樣可以通過條件查詢特定數(shù)據(jù)同時可以使用鏈?zhǔn)讲樵償?shù)據(jù)。

//查詢花費超過10元的消費記錄(使用斷言字符串查詢)
consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10")
 
//查詢花費超過10元的購物記錄(使用 NSPredicate 查詢)
let predicate = NSPredicate(format: "type.name = '購物' AND cost > 10")
consumeItems = realm.objects(ConsumeItem.self).filter(predicate)
 
//使用鏈?zhǔn)讲樵?consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10").filter("type.name = '購物'")

支持的斷言:比較操作數(shù)(comparison operand)可以是屬性名稱或者某個常量,但至少有一個操作數(shù)必須是屬性名稱;
比較操作符 ==、<=、<、>=、>、!=, 以及 BETWEEN 支持 int、long、long long、float、double 以及 NSDate 屬性類型的比較,比如說 age == 45;
相等比較 ==以及!=,比如說Results<Employee>().filter("company == %@", company)
比較操作符 == and != 支持布爾屬性;
對于 NSString 和 NSData 屬性來說,我們支持 ==、!=、BEGINSWITH、CONTAINS 以及 ENDSWITH 操作符,比如說 name CONTAINS ‘Ja’;
字符串支持忽略大小寫的比較方式,比如說 name CONTAINS[c] ‘Ja’ ,注意到其中字符的大小寫將被忽略;
Realm 支持以下復(fù)合操作符:“AND”、“OR” 以及 “NOT”。比如說 name BEGINSWITH ‘J’ AND age >= 32;
包含操作符 IN,比如說 name IN {‘Lisa’, ‘Spike’, ‘Hachi’};
==、!=支持與 nil 比較,比如說 Results<Company>().filter("ceo == nil")。注意到這只適用于有關(guān)系的對象,這里 ceo 是 Company 模型的一個屬性。
ANY 比較,比如說 ANY student.age < 21
注意,雖然我們不支持復(fù)合表達(dá)式類型(aggregate expression type),但是我們支持對對象的值使用 BETWEEN 操作符類型。比如說,Results<Person>.filter("age BETWEEN %@", [42, 43]])。

11、查詢結(jié)果的排序

//查詢花費超過10元的消費記錄,并按升序排列
consumeItems = realm.objects(ConsumeItem.self).filter("cost > 10").sorted(byProperty: "cost")

12、使用List實現(xiàn)一對多關(guān)系

List 中可以包含簡單類型的 Object,表面上和可變的 Array 非常類似。注意:List 只能夠包含 Object 類型,不能包含諸如String之類的基礎(chǔ)類型。 如果打算給我們的 Person 數(shù)據(jù)模型添加一個“dogs”屬性,以便能夠和多個“dogs”建立關(guān)系,也就是表明一個 Person 可以有多個 Dog,那么我們可以聲明一個List類型的屬性:

class Person: Object {
    ... // 其余的屬性聲明
    let dogs = List<Dog>()
}
 
// 這里我們就可以使用已存在的狗狗對象來完成初始化
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])
 
// 還可以使用多重嵌套
let aPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺財", 6]]])

可以和之前一樣,對 List 屬性進(jìn)行訪問和賦值:

let someDogs = realm.objects(Dog.self).filter("name contains '小白'")
ZhangSan.dogs.append(objectsIn: someDogs)
ZhangSan.dogs.append(dahuang)

反向關(guān)系(Inverse Relationship)通過反向關(guān)系(也被稱為反向鏈接(backlink)),您可以通過一個特定的屬性獲取和給定對象有關(guān)系的所有對象。 Realm 提供了“鏈接對象 (linking objects)” 屬性來表示這些反向關(guān)系。借助鏈接對象屬性,您可以通過指定的屬性來獲取所有鏈接到指定對象的對象。
例如,一個 Dog 對象可以擁有一個名為 owners 的鏈接對象屬性,這個屬性中包含了某些 Person 對象,而這些 Person 對象在其 dogs 屬性中包含了這一個確定的 Dog 對象。您可以將 owners 屬性設(shè)置為 LinkingObjects 類型,然后指定其關(guān)系,說明其當(dāng)中包含了 Person 對象。

class Dog: Object {
   dynamic var name = ""
   dynamic var age = 0
    
   // Realm 并不會存儲這個屬性,因為這個屬性只定義了 getter
   // 定義“owners”,和 Person.dogs 建立反向關(guān)系
   let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}

13、添加主鍵(Primary Keys) 重寫 Object.primaryKey() 可以設(shè)置模型的主鍵。聲明主鍵之后,對象將被允許查詢,更新速度更加高效,并且要求每個對象保持唯一性。一旦帶有主鍵的對象被添加到 Realm 之后,該對象的主鍵將不可修改。

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

14、添加索引屬性(Indexed Properties)重寫 Object.indexedProperties() 方法可以為數(shù)據(jù)模型中需要添加索引的屬性建立索引:

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

15、設(shè)置忽略屬性(Ignored Properties)重寫 Object.ignoredProperties() 可以防止 Realm 存儲數(shù)據(jù)模型的某個屬性。Realm 將不會干涉這些屬性的常規(guī)操作,它們將由成員變量(var)提供支持,并且您能夠輕易重寫它們的 setter 和 getter。

class Person: Object {
  dynamic var tmpID = 0
  var name: String { // 計算屬性將被自動忽略
    return "\(firstName) \(lastName)"
  }
  dynamic var firstName = ""
  dynamic var lastName = ""
 
  override static func ignoredProperties() -> [String] {
    return ["tmpID"]
  }
}

16、修改更新數(shù)據(jù)

(1)直接更新內(nèi)容

// 在一個事務(wù)中更新對象
try! realm.write {
  consumeItem.name = "去北京旅行"
}

(2)通過主鍵更新如果您的數(shù)據(jù)模型中設(shè)置了主鍵的話,那么您可以使用 Realm().add(_:update:) 來更新對象(當(dāng)對象不存在時也會自動插入新的對象。)

/****** 方式1 ***/
// 創(chuàng)建一個帶有主鍵的“書籍”對象,作為事先存儲的書籍
let cheeseBook = Book()
cheeseBook.title = "奶酪食譜"
cheeseBook.price = 9000
cheeseBook.id = 1
 
// 通過 id = 1 更新該書籍
try! realm.write {
    realm.add(cheeseBook, update: true)
}
 
/****** 方式2 ***/
// 假設(shè)帶有主鍵值 `1` 的“書籍”對象已經(jīng)存在
try! realm.write {
    realm.create(Book.self, value: ["id": 1, "price": 22], update: true)
    // 這本書的`title`屬性不會被改變
}

(3)鍵值編碼
這個是在運行時才能決定哪個屬性需要更新的時候,這個對于大量更新的對象極為有用。

let persons = realm.objects(Person.self)
try! realm.write {
    // 更新第一個
    persons.first?.setValue(true, forKeyPath: "isFirst")
    // 將每個人的 planet 屬性設(shè)置為“地球”
    persons.setValue("地球", forKeyPath: "planet")
}

17、刪除數(shù)據(jù)

let cheeseBook = ... // 存儲在 Realm 中的 Book 對象
 
// 在事務(wù)中刪除一個對象
try! realm.write {
  realm.delete(cheeseBook)
}

也能夠刪除數(shù)據(jù)庫中的所有數(shù)據(jù)

// 從 Realm 中刪除所有數(shù)據(jù)
try! realm.write {
  realm.deleteAll()
}

18、Realm數(shù)據(jù)庫配置

(1)修改默認(rèn)的的數(shù)據(jù)庫
通過調(diào)用 Realm() 來初始化以及訪問我們的 realm 變量。其指向的是應(yīng)用的 Documents 文件夾下的一個名為“default.realm”的文件。
通過對默認(rèn)配置進(jìn)行更改,我們可以使用不同的數(shù)據(jù)庫。比如給每個用戶帳號創(chuàng)建一個特有的 Realm 文件,通過切換配置,就可以直接使用默認(rèn)的 Realm 數(shù)據(jù)庫來直接訪問各自數(shù)據(jù)庫:

func setDefaultRealmForUser(username: String) {
   var config = Realm.Configuration()
    
   // 使用默認(rèn)的目錄,但是使用用戶名來替換默認(rèn)的文件名
   config.fileURL = config.fileURL!.deletingLastPathComponent()
       .appendingPathComponent("\(username).realm")
    
   // 將這個配置應(yīng)用到默認(rèn)的 Realm 數(shù)據(jù)庫當(dāng)中
   Realm.Configuration.defaultConfiguration = config
}

(2)打包進(jìn)項目里的數(shù)據(jù)庫的使用如果需要將應(yīng)用的某些數(shù)據(jù)(比如配置信息,初始化信息等)打包到一個 Realm 文件中,作為主要 Realm 數(shù)據(jù)庫的擴展,操作如下:

let config = Realm.Configuration(
    // 獲取需要打包文件的 URL 路徑
    fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"),
    // 以只讀模式打開文件,因為應(yīng)用數(shù)據(jù)包并不可寫
    readOnly: true)
 
// 通過配置打開 Realm 數(shù)據(jù)庫
let realm = try! Realm(configuration: config)
 
// 通過配置打開 Realm 數(shù)據(jù)庫
let results = realm.objects(Dog.self).filter("age > 5")

(3)內(nèi)存數(shù)據(jù)庫內(nèi)存數(shù)據(jù)庫在每次程序運行期間都不會保存數(shù)據(jù)。但是,這不會妨礙到 Realm 的其他功能,包括查詢、關(guān)系以及線程安全。 假如您需要靈活的數(shù)據(jù)讀寫但又不想儲存數(shù)據(jù)的話,那么內(nèi)存數(shù)據(jù)庫對您來說一定是一個不錯的選擇。

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

19、加密數(shù)據(jù)庫

(1)加密后的 Realm文件不能跨平臺使用(因為 NSFileProtection 只有 iOS 才可以使用)
(2)Realm 文件不能在沒有密碼保護(hù)的 iOS 設(shè)備中進(jìn)行加密。為了避免這些問題(或者您想構(gòu)建一個 OS X 的應(yīng)用),可以使用 Realm 提供的加密方法。
(3)加密過的 Realm 只會帶來很少的額外資源占用(通常最多只會比平常慢10%)。

/*****   在創(chuàng)建 Realm 數(shù)據(jù)庫時采用64位的密鑰對數(shù)據(jù)庫文件進(jìn)行 AES-256+SHA2 加密   ****/
// 產(chǎn)生隨機密鑰
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` 會提示數(shù)據(jù)庫不可訪問
    fatalError("Error opening realm: \(error)")
}
 
// 和往常一樣使用 Realm 即可
let dogs = realm.objects(Book.self).filter("name contains 'Fido'")

20、數(shù)據(jù)遷移(Migration)

(1)為何要遷移比如原來有如下 Person 模型:

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

假如我們想要更新數(shù)據(jù)模型,給它添加一個 fullname 屬性, 而不是將“姓”和“名”分離開來。

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

在這個時候如果您在數(shù)據(jù)模型更新之前就已經(jīng)保存了數(shù)據(jù)的話,那么 Realm 就會注意到代碼和硬盤上數(shù)據(jù)不匹配。 每當(dāng)這時,您必須進(jìn)行數(shù)據(jù)遷移,否則當(dāng)您試圖打開這個文件的話 Realm 就會拋出錯誤。 (2)如何進(jìn)行數(shù)據(jù)遷移假設(shè)我們想要把上面所聲明 Person 數(shù)據(jù)模型進(jìn)行遷移。如下所示是最簡單的數(shù)據(jù)遷移的必需流程:

// 在(application:didFinishLaunchingWithOptions:)中進(jìn)行配置
 
let config = Realm.Configuration(
  // 設(shè)置新的架構(gòu)版本。這個版本號必須高于之前所用的版本號
  // (如果您之前從未設(shè)置過架構(gòu)版本,那么這個版本號設(shè)置為 0)
  schemaVersion: 1,
 
  // 設(shè)置閉包,這個閉包將會在打開低于上面所設(shè)置版本號的 Realm 數(shù)據(jù)庫的時候被自動調(diào)用
  migrationBlock: { migration, oldSchemaVersion in
    // 目前我們還未進(jìn)行數(shù)據(jù)遷移,因此 oldSchemaVersion == 0
    if (oldSchemaVersion < 1) {
      // 什么都不要做!Realm 會自行檢測新增和需要移除的屬性,然后自動更新硬盤上的數(shù)據(jù)庫架構(gòu)
    }
  })
 
// 告訴 Realm 為默認(rèn)的 Realm 數(shù)據(jù)庫使用這個新的配置對象
Realm.Configuration.defaultConfiguration = config
 
// 現(xiàn)在我們已經(jīng)告訴了 Realm 如何處理架構(gòu)的變化,打開文件之后將會自動執(zhí)行遷移
let realm = try! Realm()

雖然這個遷移操作是最精簡的了,但是我們需要讓這個閉包能夠自行計算新的屬性(這里指的是 fullName),這樣才有意義。 在遷移閉包中,我們能夠調(diào)用Migration().enumerateObjects(::) 來枚舉特定類型的每個 Object 對象,然后執(zhí)行必要的遷移邏輯。注意,對枚舉中每個已存在的 Object 實例來說,應(yīng)該是通過訪問 oldObject 對象進(jìn)行訪問,而更新之后的實例應(yīng)該通過 newObject 進(jìn)行訪問:

// 在 application(application:didFinishLaunchingWithOptions:) 中進(jìn)行配置
 
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
        // 將名字進(jìn)行合并,存放在 fullName 域中
        let firstName = oldObject!["firstName"] as! String
        let lastName = oldObject!["lastName"] as! String
        newObject!["fullName"] = "\(firstName) \(lastName)"
      }
    }
  })

21、使用帶有 REST API 功能的 Realm 數(shù)據(jù)庫示例我們將從 豆瓣FM的API 那里獲取一組 JSON 格式的頻道數(shù)據(jù),然后將它以 Realm Objects 的形式儲存到默認(rèn)的 Realm 數(shù)據(jù)庫里。

(1)json數(shù)據(jù)格式如下:

{
  "channels" : [
    {
      "channel_id" : 0,
      "abbr_en" : "My",
      "name_en" : "Personal Radio",
      "seq_id" : 0,
      "name" : "私人兆赫"
    },
    {
      "channel_id" : "1",
      "abbr_en" : "",
      "seq_id" : 0,
      "name" : "華語",
      "name_en" : ""
    },
    {
      "channel_id" : "2",
      "abbr_en" : "",
      "seq_id" : 1,
      "name" : "歐美",
      "name_en" : ""
    }  ]
}

(2)我們將直接把 Dictionary 插入到 Realm 中,然后讓 Realm 自行快速地將其映射到 Object 上。(從 iOS9 起,新特性要求App訪問網(wǎng)絡(luò)請求,要采用 HTTPS 協(xié)議。直接請求HTTP數(shù)據(jù)會報錯,解決辦法可以參照的我另一篇文章:Swift - 網(wǎng)絡(luò)請求報App Transport Security has blocked a cleartext錯)為了確保示例能夠成功,我們需要一個所有屬性完全匹配 JSON 鍵結(jié)構(gòu)的 Object 結(jié)構(gòu)體。如果 JSON 的鍵結(jié)構(gòu)不匹配 Object 結(jié)構(gòu)體屬性結(jié)構(gòu)的話,那么就會在插入時被忽略。

  fileprivate func setJsonToRealm (){
        
        // 調(diào)用API
        let url = URL(string: "https://www.douban.com/j/app/radio/channels")!
        let resopne = try! Data(contentsOf: url)
        
        let json = try! JSONSerialization.jsonObject(with: resopne, options: .allowFragments) as! [String: Any]
        let channels = json["channels"] as! [[String: Any]]
        
        try! realm.write {
            for channel in channels {
                if channel["seq_id"] as! Int == 0 {continue}//第一個頻道數(shù)據(jù)有問題,丟棄三
                realm.create(DoubanChannel.self, value: channel, update: true)
                
            }
        }
        
        
    }

(3)可以看到數(shù)據(jù)已經(jīng)成功插入到庫中了

22、當(dāng)前版本的限制Realm 致力于平衡數(shù)據(jù)庫讀取的靈活性和性能。為了實現(xiàn)這個目標(biāo),在 Realm 中所存儲的信息的各個方面都有基本的限制。例如:

(1)類名稱的長度最大只能存儲 57 個 UTF8 字符。
(2)屬性名稱的長度最大只能支持 63 個 UTF8 字符。
(3)NSData 以及 String 屬性不能保存超過 16 MB 大小的數(shù)據(jù)。如果要存儲大量的數(shù)據(jù),可通過將其分解為16MB 大小的塊,或者直接存儲在文件系統(tǒng)中,然后將文件路徑存儲在 Realm 中。如果您的應(yīng)用試圖存儲一個大于 16MB 的單一屬性,系統(tǒng)將在運行時拋出異常。
(4)對字符串進(jìn)行排序以及不區(qū)分大小寫查詢只支持“基礎(chǔ)拉丁字符集”、“拉丁字符補充集”、“拉丁文擴展字符集 A” 以及”拉丁文擴展字符集 B“(UTF-8 的范圍在 0~591 之間)。

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

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

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