【iOS 開發】Realm Swift 數據庫使用匯總

Realm

由于最近公司需要將項目用 Swift 改寫,項目中需要大量使用數據庫,之前 OC 使用的是 Core DataCore Data 使用起來確實十分的繁瑣,故決定在 Swift 中棄用,改用 Realm 數據庫,下面將使用方法記錄下來方便以后查看。


Realm 的優點

Realm 不是基于 Core Data,也不是基于 SQLite 封裝構建的。它有自己的數據庫存儲引擎,下面說一下 Realm 的一些優點。

  • 跨平臺: 現在很多應用都是要兼顧 iOSAndroid 兩個平臺同時開發。如果兩個平臺都能使用相同的數據庫,那就不用考慮內部數據的架構不同,使用 Realm 提供的 API,可以使數據持久化層在兩個平臺上無差異化的轉換。代碼可以使用 SwiftObjective-C 以及 Java 語言來編寫。

  • 簡單易用: Core DataSQLite 冗余、繁雜的知識和代碼足以嚇退絕大多數剛入門的開發者,而換用 Realm,則可以極大地減少學習成本,立即學會本地化存儲的方法。大部分常用的功能(比如插入、查詢等)都可以用一行簡單的代碼輕松完成,毫不吹噓的說,把官方最新文檔完整看一遍,就完全可以上手開發了,這是 中文官方文檔地址

  • 可視化: Realm 還提供了一個輕量級的數據庫查看工具,在 Mac Appstore 可以下載 Realm Browser 這個工具,開發者可以查看數據庫當中的內容,執行簡單的插入和刪除數據的操作。

Realm Browser

Realm Swift 的安裝

這是 RealmGitHub 地址 ,其他方法我就不說了,我是用 CocoaPods 方式安裝的,所以就只說 CocoaPods 的安裝方法了。

  • 安裝 CocoaPods 0.39.0 或者更高版本

  • 運行 pod repo update ,以確保 CocoaPods 能夠獲取到 Realm 的最新版本。

  • 在你的 Podfile 中,添加 use_frameworks!pod 'RealmSwift' 到你的主要和測試目標。

  • 如果你使用的是 Xcode 8,那么將下面代碼復制到你的 Podfile 底部,以便在必要的時候更新 Swift 的版本。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.0'
    end
  end
end
  • 在終端運行 pod install

  • 采用 CocoaPods 生成的 .xcworkspace 來運行工程。

  • 在需要使用 Realm Swift 的地方加入 import RealmSwift

Realm Browser 的使用

先說一下 Realm Browser 這個數據庫查看工具的使用方法。

1. 模擬器調試

  • 如果是使用模擬器進行調試,首先通過以下代碼打印出 Realm 數據庫地址。
let realm = try! Realm()
print(realm.configuration.fileURL!)
  • 然后打開 Finder 按下 command + shift + G 跳轉到對應路徑下,用 Realm Browser 打開對應的 .realm 文件就可以看到數據了。
.realm 文件

2. 真機調試

  • 如果是真機調試的話,打開 Xcode ,選擇菜單 Window 下的 Devices
Devices
  • 選擇對應的設備與項目,點擊 Download Container
Download Container
  • 導出 xcappdata 文件后,顯示包內容,進到 AppData 下的 Documents ,使用 Realm Browser 打開 .realm 文件即可。

Realm Swift 的使用

1. 配置 Realm 數據庫

  • 將以下代碼寫在 AppDelegatedidFinishLaunchingWithOptions 方法中,這個方法主要用于數據模型屬性增加或刪除時的數據遷移,每次模型屬性變化時,將 schemaVersion1 即可,Realm 會自行檢測新增和需要移除的屬性,然后自動更新硬盤上的數據庫架構,移除屬性的數據將會被刪除。
/* Realm 數據庫配置,用于數據庫的迭代更新 */
let schemaVersion: UInt64 = 0
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
    
    /* 什么都不要做!Realm 會自行檢測新增和需要移除的屬性,然后自動更新硬盤上的數據庫架構 */
    if (oldSchemaVersion < schemaVersion) {}
})
Realm.Configuration.defaultConfiguration = config
Realm.asyncOpen { (realm, error) in
    
    /* Realm 成功打開,遷移已在后臺線程中完成 */
    if let _ = realm {
        
        print("Realm 數據庫配置成功")
    }
    /* 處理打開 Realm 時所發生的錯誤 */
    else if let error = error {
        
        print("Realm 數據庫配置失敗:\(error.localizedDescription)")
    }
}
  • 如果屬性改變后,想要保留原來已存在的數據來更新新的屬性值,在屬性變化后將 schemaVersion1 ,并將 config 改為如下,其余不變。
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
    
    if (oldSchemaVersion < schemaVersion) {
    
        migration.enumerateObjects(ofType: Dog.className()) { oldObject, newObject in
            
            /* 將 Dog 表中舊的 firstName 和 lastName 屬性刪除,數據保留合并為 fullName 屬性 */
            let firstName = oldObject!["firstName"] as! String
            let lastName = oldObject!["lastName"] as! String
            newObject!["fullName"] = "\(firstName) \(lastName)"
        }
    }
})
  • 如果是只是屬性重命名,想保留原來已經存在的數據,重命名以后將 schemaVersion1 ,并將 config 改為如下,其余不變,并且重命名操作應該在調用上面 enumerateObjects(ofType: _:) 之外完成。
let config = Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
    
    if (oldSchemaVersion < schemaVersion) {
    
        /* 將 Dog 表的 name 屬性重命名為 fullName */
        migration.renameProperty(onType: Dog.className(), from: "name", to: "fullName")
    }
})

2. Model 數據模型

Realm 數據模型是基于標準 Swift 類來進行定義的,使用屬性來完成模型的具體定義,Realm 模型對象在形式上基本上與其他 Swift 對象相同,你可以給它們添加您自己的方法和協議,和在其他對象中使用類似。

Realm 支持的屬性類

Realm 支持這幾種屬性類型:BoolInt8Int16Int32Int64DoubleFloatStringNSDate 以及 NSData ,下面的表格提供了關于聲明模型屬性的簡易參考。

類型 非可選值形式 可選值形式
Bool dynamic var value = false let value = RealmOptional<Bool>()
Int dynamic var value = 0 let value = RealmOptional<Int>()
Float dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
Double dynamic var value: Double = 0.0 let value = RealmOptional<Double>()
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 必須是可選值 dynamic var value: Class?
List let value = List<Class>() 必須是非可選值
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") 必須是非可選值

Model 數據模型創建

下面以 DogPerson 為例,通過簡單的繼承 Object 或者一個已經存在的模型類,你就可以創建一個新的 Realm 數據模型對象。

普通的數據模型
/// 狗狗的數據模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0
}

/// 狗狗主人的數據模型
class Person: Object {

    dynamic var name: String?
    dynamic var birthdate = NSDate()
}
關系綁定
/// 狗狗的數據模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0
    dynamic var owner: Person?  // 對一關系
}

/// 狗狗主人的數據模型
class Person: Object {

    dynamic var name: String?
    dynamic var birthdate = NSDate()
    let dogs = List<Dog>()  // 對多關系
}
反向關系

如果對多關系屬性 Person.dogs 鏈接了一個 Dog 實例,而這個實例的對一關系屬性 Dog.owner 又鏈接到了對應的這個 Person 實例,那么實際上這些鏈接仍然是互相獨立的。

Person 實例的 dogs 屬性添加一個新的 Dog 實例,并不會將這個 Dog 實例的 owner 屬性自動設置為該 Person

但是由于手動同步雙向關系會很容易出錯,并且這個操作還非常得復雜、冗余,因此 Realm 提供了 鏈接對象 (linking objects) 屬性來表示這些反向關系。

/// 狗狗的數據模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0
    let owner = LinkingObjects(fromType: Person.self, property: "dogs") // 反向關系
}

/// 狗狗主人的數據模型
class Person: Object {

    dynamic var name: String?
    dynamic var birthdate = NSDate()
    let dogs = List<Dog>()  // 對多關系
}
索引屬性(Indexed Properties)

重寫 Object.indexedProperties() 方法可以為數據模型中需要添加索引的屬性建立索引。Realm 支持字符串整數布爾值 以及 NSDate 屬性作為索引。對屬性進行索引可以減少插入操作的性能耗費,加快比較檢索的速度(比如說 = 以及 IN 操作符)

/// 狗狗的數據模型
class Dog: Object {

    dynamic var name: String?
    dynamic var age = 0

    override class func indexedProperties() -> [String] {

        return ["name"]
    }
}
主鍵(Primary Keys)

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

Realm 可以將 IntString 類型的屬性設為主鍵,但是不支持自增長屬性,所以只能自己給主鍵生成一個唯一的標識,可以使用 UUID().uuidString 方法生成唯一主鍵。

/// 狗狗的數據模型
class Dog: Object {
    
    dynamic var id = UUID().uuidString
    dynamic var name: String?
    dynamic var age = 0
    
    override class func primaryKey() -> String? {

        return "id" 
    }
}
忽略屬性(Ignored Properties)

重寫 Object.ignoredProperties() 可以防止 Realm 存儲數據模型的某個屬性。Realm 將不會干涉這些屬性的常規操作,它們將由成員變量提供支持,并且您能夠輕易重寫它們的 settergetter

/// 狗狗的數據模型
class Dog: Object {
    
    dynamic var name: String?
    dynamic var age = 0
    
    override class func ignoredProperties() -> [String]? {

        return ["name"] 
    }
}

3. 創建數據模型對象

  • 可以用多種方法創建一個新的對象:
/* (1) 創建一個狗狗對象,然后設置其屬性 */
var myDog = Dog()
myDog.name = "大黃"
myDog.age = 10

/* (2) 通過字典創建狗狗對象 */
let myOtherDog = Dog(value: ["name" : "豆豆", "age": 3])

/* (3) 通過數組創建狗狗對象 */
let myThirdDog = Dog(value: ["豆豆", 5])
  • 即使是數組以及字典的多重嵌套,Realm 也能夠輕松完成對象的創建。注意 List 只能夠包含 Object 類型,不能包含諸如 String 之類的基礎類型。
/* 這里我們就可以使用已存在的狗狗對象來完成初始化 */
let aPerson = Person(value: ["李四", 30, [aDog, anotherDog]])

/* 還可以使用多重嵌套 */
let anotherPerson = Person(value: ["李四", 30, [["小黑", 5], ["旺財", 6]]])

4. 數據庫操作(增刪改查)

任何操作都需要獲取 Realm 實例,每個線程只需要使用一次即可。

/* 獲取默認的 Realm 實例,每個線程只需要使用一次即可 */
let realm = try! Realm()

增加數據

/* 創建一個 Dog 對象 */
let dog = Dog(value: ["name" : "豆豆", "age": 3])

/* 創建一個 Dog 對象數組 */
let dogs = [Dog(value: ["name": "張三", "age": 1]), Dog(value: ["name": "李四", "age": 2]), Dog(value: ["name": "王五", "age": 3])]

/* 通過事務將數據添加到 Realm 中 */
try! realm.write {

    realm.add(dog) // 增加單個數據
    realm.add(dogs) // 增加多個數據
    realm.create(Dog.self, value: ["name" : "豆豆", "age": 3], update: true) // 直接根據 JSON 數據增加
}

刪除數據

// let dog = ... 存儲在 Realm 中的 Dog 對象
// let dogs = ... 存儲在 Realm 中的多個 Dog 對象

/* 在事務中刪除數據 */
try! realm.write {

    realm.delete(dog) // 刪除單個數據
    realm.delete(dogs) // 刪除多個數據
    realm.deleteAll() // 從 Realm 中刪除所有數據
}

修改數據

  • 內容直接更新: 在事務中直接修改某一條數據。
// let dog = ... 存儲在 Realm 中的 Dog 對象

/* 在一個事務中修改數據 */
try! realm.write {

    dog.name = "張三"
}
  • ** 通過主鍵更新: ** 如果你的數據模型中設置了主鍵的話,那么你可以使用 Realm().add(_:update:) 來更新數據,如果數據不存在時會自動插入新的數據。
// let dog = ... 存儲在 Realm 中的 Dog 對象(有主鍵)
// let dogs = ... 存儲在 Realm 中的多個 Dog 對象(有主鍵)

/* 在一個事務中修改數據 */
try! realm.write {

    realm.add(dog, update: true) // 更新單個數據
    realm.add(dogs, update: true) // 更新多個數據
}
  • 鍵值編碼: ObjectResult 以及 List 都遵守 鍵值編碼(Key-Value Coding) 機制。 當你在運行時才能決定哪個屬性需要更新的時候,這個方法是最有用的。將 KVC 應用在集合當中是大量更新對象的極佳方式,這樣就可以不用經常遍歷集合,為每個項目創建一個訪問器了。
// let dogs = ... 存儲在 Realm 中的多個 Dog 對象

/* 在一個事務中修改數據 */
try! realm.write {

    dogs.first?.setValue("張三", forKeyPath: "name") // 將第一個狗狗名字改為張三
    dogs.setValue("張三", forKeyPath: "name") // 將所有狗狗名字都改為張三
}

查詢數據

  • 普通查詢: 查詢數據庫中某張表的所有數據。
/* 從數據庫中查詢所有狗狗 */
let dogs = realm.objects(Dog.self)
  • 主鍵查詢: 根據 主鍵 查詢某張表的某條數據,模型必須包含主鍵,否則會崩潰。
/* 從數據庫中查詢主鍵為 1 的狗狗 */
let dog = realm.object(ofType: Dog.self, forPrimaryKey: "1")
  • 條件查詢: 根據 斷言字符串 或者 NSPredicate 謂詞 查詢某張表中的符合條件數據。
/* 根據斷言字符串從數據庫查詢 name 為 張三 的狗狗 */
var dogs = realm.objects(Dog.self).filter("name = %@", "張三")

/* 根據 NSPredicate 謂詞從數據庫查詢 age 小于 5 并且 name 以 ‘張’ 開頭的狗狗 */
let predicate = NSPredicate(format: "age < 5 AND name BEGINSWITH '張'")
var dogs = realm.objects(Dog.self).filter(predicate)
  • 排序查詢: 將查詢結果進行排序,可以和條件查詢配合使用。
/* 將查詢到的狗狗根據名字升序進行排序 */
let dogs = realm.objects(Dog.self).sorted(byKeyPath: "name")

/* 將查詢到的狗狗根據名字降序進行排序 */
let dogs = realm.objects(Dog.self).sorted(byKeyPath: "name", ascending: false)

/* 將查詢到的狗狗根據名字和年齡升序進行排序 */
let dogs = realm.objects(Dog.self).sorted(by: ["name", "age"])

想要了解更多可以查看 中文官方文檔地址 ,有不足之處之后會補充,OC 版本的話可以看這篇文章:Realm數據庫 從入門到“放棄” ,寫的非常詳細,也參考了不少這篇文章的內容。

將來的你,一定會感激現在拼命的自己,愿自己與讀者的開發之路無限美好。

我的傳送門: 博客簡書微博GitHub

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容