Realm for Swift

我要在河邊釣一整天的魚發表於2019-03-04

Realm Studio

使用 Realm Studio,讓開發更有效率。
您可以輕鬆地開啟任何 Realm 資料庫檔案或者 Realm 物件伺服器部署物件,並對其進行分析。Realm Studio 允許您輕鬆地開啟並編輯本地 Realm 資料庫和可同步 Realm 資料庫,此外還可以管理 Realm 平臺。可執行在 Mac、Windows 以及 Linux 平臺。

下載連結

Realm 開發工具

專案中新增RealmSwift

Carthage

  1. Cartfile新增github "realm/realm-cocoa"
  2. 終端中cd到工程檔案;
  3. 執行 carthage update --platform iOS
  4. Carthage/Build/iOS/中將Realm.frameworkRealmSwift.framework新增到Xcode 工程的 “General” 設定選項卡的 “Linked Frameworks and Libraries” 部分內;
  5. 路徑配置如下圖。Carthage傳送門

配置Carthage路徑

CocoaPods

  1. 執行 pod repo update,從而讓 CocoaPods 更新至目前最新可用的 Realm 版本;
  2. 在您的 Podfile 中,將 use_frameworks! 和 pod 'RealmSwift' 新增到主應用目標和測試目標中;
  3. 在命令列中執行 pod install;
  4. 使用由 CocoaPods 生成的 .xcworkspace 檔案來編寫工程。

動態庫

下載地址

前往 Xcode 工程的 “General” 設定選項卡中,在 ios/、osx/、tvos/ 或者 watchos/ 目錄中選擇適合您專案的 Swift 版本目錄,將 Realm.framework 和 RealmSwift.framework 拖曳到 “Embedded Binaries” 部分內。請確保勾選了 Copy items if needed(除非專案中有多個平臺都需要使用 Realm ),然後單擊 Finish 按鈕; 在單元測試目標的 “Build Settings” 中,將 RealmSwift.framework 的父目錄新增到 “Framework Search Paths” 部分中; 如果在 iOS、watchOS 或者 tvOS 工程中使用 Realm,請在應用目標的 “Build Phases” 中建立一條新的 “Run Script Phase”,然後將下面這段程式碼貼上到指令碼文字框內:

Copy to clipboardbash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh" 因為要繞過App Store 出現的提交 bug,因此這一步在打包通用二進位制檔案時是必須的。

使用RealmSwift

資料模型

1.建立資料模型

在使用Realm中儲存的資料模型都要是Object類的子類。

import Foundation
import RealmSwift

class Dog: Object {
   @objc dynamic var id: Int = 0
   @objc dynamic var name: String?
    
}

class Cat: Object {
    @objc dynamic var name: String?
}

複製程式碼

@objc是為了相容Swift4

2.支援的屬性型別

Realm支援的型別:Bool、Int、Int8、Int16、Int32、Int64、Double、Float、String、Date 以及 Data。 其中String、Date 以及 Data 屬性都是可空的,Object 屬性必須可空。

在上面Dog中你會發現數值型別是不能寫成Int?型別的,在Realm中可選數值型別要如下設定:

  // 可選 int 屬性,預設為 nil
  // RealmOption 屬性應該始終用 `let` 進行宣告,
  // 因為直接對其進行賦值並不會起任何作用
  let age = RealmOptional<Int>()
    
複製程式碼
let realm = try! Realm()
try! realm.write() {
    var person = realm.create(Person.self, value: ["Jane", 27])
    // // 讀取或者修改 `RealmOptional` 可以通過 `value` 屬性實現
    person.age.value = 28
}

複製程式碼

RealmOptional 支援 Int、Float、Double、Bool,以及所有大小的 Int 版本(包括 Int8、Int16、Int32、Int64)。

3.設定主鍵

override static func primaryKey() -> String? {
    return "id"
}
複製程式碼

實現這個方法就能直接設定主鍵

4.索引屬性

override static func indexedProperties() -> [String] {
   return ["title"]
}    
複製程式碼

實現indexedProperties進行設定索引屬性。

Realm 支援為字串、整型、布林值以及 Date 屬性建立索引。

5.屬性備忘單

型別 非可空值形式 可空值形式
Bool @objc dynamic var value = false let value = RealmOptional()
Int @objc dynamic var value = 0 let value = RealmOptional()
Float @objc dynamic var value: Float = 0.0 let value = RealmOptional()
Double @objc dynamic var value: Double = 0. let value = RealmOptional()
String @objc dynamic var value = "" @objc dynamic var value: String? = nil
Data @objc dynamic var value = Data() @objc dynamic var value: Data? = nil
Date @objc dynamic var value = Date() @objc dynamic var value: Date? = ni
Object 不存在:必須是可空值 @objc dynamic var value: Class?
List let value = List() 不存在:必須是非可空值
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") 不存在:必須是非可空值

建立Realm

//MARK: - 建立資料庫
    /// 建立資料庫
    ///
    /// - Parameters:
    ///   - dataBaseName: 資料庫名字
    ///   - isReadOnly: 是否是隻讀
    /// - Returns: Realm例項
    @discardableResult
    public func creatDB(_ dataBaseName: String, isReadOnly: Bool = false) -> Realm? {
        let realm = openDB(dataBaseName, isReadOnly: isReadOnly)
        return realm
    }
    
    /// 開啟資料庫
    ///
    /// - Parameter name: 資料庫名字
    /// - isReadOnly: 是否是隻讀
    /// - Returns: Realm例項
    @discardableResult
    private func openDB(_ dataBaseName: String, isReadOnly: Bool = false) -> Realm? {
        guard let dbPath = getCreatDatabasePath(dataBaseName) else {
            return nil
        }
        var config = Realm.Configuration()
        config.fileURL = dbPath
        config.readOnly = isReadOnly
        Realm.Configuration.defaultConfiguration = config
        do {
            let realm = try Realm.init(configuration: config)
            return realm
        }catch let error {
            mPrint("開啟或者建立資料庫失敗:\n\(error.localizedDescription)")
            return nil
        }
    }
複製程式碼

在本地生成realm.realm檔案時還會有:

  • realm.lock - 資源鎖定檔案;
  • realm.management - 存放程式鎖檔案的目錄;
  • realm.note - 用於通知的命名管道。
    這些檔案只是Realm維護的檔案刪除或者怎麼著都不會出現什麼問題。

在報告 Realm 問題的時候,請將這些輔助檔案 (auxiliary Realm) 連同主要的 .realm 檔案一同提交,因為它們很可能會包含某些對除錯問題有用的資訊。

開啟建立的檔案利用RealmStudioda開啟會發現,在工程中所有繼承Object的類直接在Realm中建立了表(暫時未找到具體的原因)。

開啟工程中的Realm檔案

	/// 開啟預植的資料庫
    ///
    /// - Parameters:
    ///   - dataBaseName: 資料庫名字
    ///   - isReadOnly: 是否是隻讀
    /// - Returns: Realm例項
    @discardableResult
    public func openReferenceDB(_ dataBaseName: String, isReadOnly: Bool = true) -> Realm? {
        guard let dbPath = getReferenceDatabasePaeh(dataBaseName) else {
            return nil
        }
        var config = Realm.Configuration()
        config.fileURL = dbPath
        config.readOnly = isReadOnly
        Realm.Configuration.defaultConfiguration = config
        do {
            let realm = try Realm.init(configuration: config)
            return realm
        }catch let error {
            mPrint("開啟或者建立資料庫失敗:\n\(error.localizedDescription)")
            return nil
        }
    }
複製程式碼

設定Realm的defaultConfiguration

	/// 設定通過Realm()獲取資料庫的配置
    ///
    /// - Parameters:
    ///   - realmName: 資料庫的名字
    ///   - isReadOnly: 是否是這是隻讀
    public func setDefaltRealmConfiguration(_ realmName: String,isReference: Bool = false, isReadOnly: Bool = false) -> Bool{
        var realmPath: URL?
        if isReference {
            realmPath = getReferenceDatabasePaeh(realmName)
        }else {
            realmPath = getCreatDatabasePath(realmName)
        }
        if realmPath == nil {
            return false
        }
        var config = Realm.Configuration()
        config.fileURL = realmPath
        config.readOnly = isReadOnly
        Realm.Configuration.defaultConfiguration = config
        return true
    }
複製程式碼

獲取當前預設的資料庫

	/// 獲取當前預設的資料
    ///
    /// - Returns: 返回預設的Realm的資料庫例項
    @discardableResult
    public func getDefaultRealm() -> Realm? {
        do {
            return try Realm()
        }catch let error {
            mPrint("獲取預設的Realm的資料庫失敗:\n\(error.localizedDescription)")
            return nil
        }
    }
複製程式碼

如果沒有配置defaultConfiguration則會獲取預設的資料庫。

/Library/Developer/CoreSimulator/Devices
/26B4D5CC-1EF4-4897-8F02-BCFBE06F7C40/data
/Containers/Data/Application/7CDCBAF4-A7A2-45E4-9B8A-725E873975AD/Documents/default.realm
複製程式碼

配置後會獲取到設定路徑的資料庫。

/Library/Developer/CoreSimulator/Devices
/26B4D5CC-1EF4-4897-8F02-BCFBE06F7C40/data
/Containers/Data/Application/E050DEE4-71FB-4866-A10C-CBADA288D35C/Library/Caches/DB/2237DB/2237DB.realm
複製程式碼

Realm的例項不用全域性資料共享,在配置預設資料庫後你無論在什麼地方獲取的Realm()都是同一個資料庫。

 	//MARK: - 增
    /// 建立表 || 更新表
    ///
    /// - Parameters:
    ///   - type: 表向對應的物件
    ///   - value: 值
    ///   - update: 是否是更新, 如果是"true", Realm會查詢物件並更新它, 否則新增物件
    ///   - result: 最後新增物件是成功, 如果成功將物件返回
    public func creatObject(_ type: RealmSwift.Object.Type, value: Any? = nil, update: Bool = false, result: ((RealmSwift.Object?, Error?) -> Void)? = nil){
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                let object = (value == nil) ? realm?.create(type) : realm?.create(type, value: value!, update: update)
                result?(object, nil)
            }
        } catch let error {
            mPrint("獲取預設的Realm的資料庫失敗:\n\(error.localizedDescription)")
            result?(nil, error)
        }
    }
    
複製程式碼
    /// 新增資料 || 根據主鍵更新資料
    ///
    /// - Parameters:
    ///   - object: 要新增的資料
    ///   - update: 是否更新, 如果是true
    ///   - result: 新增資料的狀態
    public func addObject(_ object: RealmSwift.Object, update: Bool = false, result: ((Error?) -> Void)? = nil) {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                realm?.add(object, update: update)
                result?(nil)
            }
        } catch let error {
            mPrint("新增資料失敗:\n \(error.localizedDescription)")
            result?(error)
        }
    }
複製程式碼

//MARK: - 刪
    /// 刪除資料
    ///
    /// - Parameters:
    ///   - object: 要刪除的物件
    ///   - result: 刪除的狀態
    public func deleteObject(_ object: RealmSwift.Object, result: ((Error?) -> Void)? = nil) {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                realm?.delete(object)
                result?(nil)
            }
        } catch let error {
            mPrint("新增資料失敗:\n \(error.localizedDescription)")
            result?(error)
        }
    }
複製程式碼
    
    /// 刪除當前資料庫中所有的資料
    ///
    /// - Parameter result: 刪除的狀態
    public func deleteAllObject(result: ((Error?) -> Void)? = nil) {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                realm?.deleteAll()
                result?(nil)
            }
        } catch let error {
            mPrint("新增資料失敗:\n \(error.localizedDescription)")
            result?(error)
        }
    }
複製程式碼
    /// 刪除當前開啟的資料庫
    ///
    /// - Parameter dataBaseName: 資料庫的名字
    /// - Returns: 刪除的狀態
    @discardableResult
    public func deleteCreatDBFile() -> Bool {
       return  autoreleasepool { () -> Bool in
            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)
                    return true
                } catch {
                    // 錯誤處理
                    return false
                }
            }
            return false
        }
    }
複製程式碼

//MARK: - 改
    /// 根據主鍵進行更新
    ///
    /// - Parameters:
    ///   - object: 要更新的物件
    ///   - update: 是否根據主鍵更新, 如果是"false"則是新增資料
    ///   - result: 更新資料的結果
    public func updateObject(_ object: RealmSwift.Object, update: Bool = true, result: ((Error?) -> Void)? = nil) {
        addObject(object, update: update, result: result)
    }
複製程式碼
    
    /// 根據主鍵進行更新
    ///
    /// - Parameters:
    ///   - type: 要更新的物件型別
    ///   - value: 要更新的值, 例如: ["id": 1, "price": 9000.0]
    ///   - update: 是否根據主鍵進行更新, 如果為"false"則為建立表
    ///   - result: 更新的結果
    public func updateObject(_ type: RealmSwift.Object.Type, value: Any? = nil, update: Bool = true, result: ((RealmSwift.Object?, Error?) -> Void)? = nil) {
        creatObject(type, value: value, update: update, result: result)
    }
複製程式碼
    
    /// 直接更新物件
    ///
    /// - Parameters:
    ///   - property: 要更改的屬性
    ///   - value: 更改的值
    /// - Returns: 更改的結果
    @discardableResult
    public func updateObject( property: inout Any, value: Any) -> Bool {
        let realm = getDefaultRealm()
        do {
            try realm?.write {
                 property = value
            }
            return true
        } catch let error {
            mPrint("直接更新物件屬性錯誤: \(error.localizedDescription)")
            return false
        }
    }
複製程式碼
    
    /// 更改表中所有的欄位的值
    ///
    /// - Parameters:
    ///   - type: 表的物件型別
    ///   - key: 要更改的欄位名
    ///   - value: 更改的值
    /// - Returns: 返回更改結果
    public func updateObjects(type: RealmSwift.Object.Type, key: String, value: Any) -> Bool {
        let objects = getObjects(type: type)
        do {
            try getDefaultRealm()?.write {
                objects?.setValue(value, forKeyPath: key)
            }
            return true
        } catch let error {
            mPrint("更改一個表中的所有資料錯誤: \(error.localizedDescription)")
            return false
        }
    }
複製程式碼
    
    /// 根據主鍵進行對某個物件中的資料進行更新
    ///
    /// - Parameters:
    ///   - type: 表型別
    ///   - primaryKey: 主鍵
    ///   - key: 要更改屬性
    ///   - value: 更改的值
    /// - Returns: 更改的狀態
    public func updateObject(type: RealmSwift.Object.Type, primaryKey: Any, key: String, value: Any) -> Bool {
        let object = getObjectWithPrimaryKey(type: type, primaryKey: primaryKey)
        do {
            try getDefaultRealm()?.write {
                object?.setValue(value, forKeyPath: key)
            }
            return true
        } catch let error {
            mPrint("更新資料出錯: \(error.localizedDescription)")
            return false
        }
    }
複製程式碼

//MARK: - 查
    /// 查詢一個表中的所有的資料
    ///
    /// - Parameter type: 物件型別
    /// - Returns: 查到的資料
    public func getObjects(type: RealmSwift.Object.Type) -> RealmSwift.Results<RealmSwift.Object>?{
         return getDefaultRealm()?.objects(type)
    }
複製程式碼
    /// 根據主鍵查詢某個物件
    ///
    /// - Parameters:
    ///   - type: 物件型別
    ///   - primaryKey: 主鍵
    /// - Returns: 查到的資料
    public func getObjectWithPrimaryKey(type: RealmSwift.Object.Type, primaryKey: Any) -> RealmSwift.Object? {
        return getDefaultRealm()?.object(ofType: type, forPrimaryKey: primaryKey)
    }
複製程式碼
    
    /// 使用斷言字串查詢
    ///
    /// - Parameters:
    ///   - type: 物件型別
    ///   - filter: 過濾條件
    /// - Returns: 查詢到的資料
    /// - example:
    ///   - var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")
    public func getObject(type: RealmSwift.Object.Type, filter: String) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObjects(type: type)?.filter(filter)
    }
複製程式碼
    
    /// 使用謂詞進行查詢
    ///
    /// - Parameters:
    ///   - type: 物件型別
    ///   - predicate: 謂詞物件
    /// - Returns: 查詢到的資料
    /// - example:
    ///   - let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
    ///   - tanDogs = realm.objects(Dog.self).filter(predicate)
    public func getObject(type: RealmSwift.Object.Type, predicate: NSPredicate) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObjects(type: type)?.filter(predicate)
    }
複製程式碼

    /// 對查詢的資料進行排序,請注意, 不支援 將多個屬性用作排序基準,此外也無法鏈式排序(只有最後一個 sorted 呼叫會被使用)。 如果要對多個屬性進行排序,請使用 sorted(by:) 方法,然後向其中輸入多個 SortDescriptor 物件。
    ///
    /// - Parameters:
    ///   - type: 物件型別
    ///   - filter: 過濾條件
    ///   - sortedKey: 需要排序的欄位
    /// - Returns: 最後的結果
    public func getObject(type: RealmSwift.Object.Type, filter: String, sortedKey: String) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObject(type: type, filter: filter)?.sorted(byKeyPath: sortedKey)
    }
複製程式碼
    
    /// 對查詢的資料進行排序, 請注意, 不支援 將多個屬性用作排序基準,此外也無法鏈式排序(只有最後一個 sorted 呼叫會被使用)。 如果要對多個屬性進行排序,請使用 sorted(by:) 方法,然後向其中輸入多個 SortDescriptor 物件。
    ///
    /// - Parameters:
    ///   - type: 隊形型別
    ///   - predicate: 謂詞物件
    ///   - sortedKey: 排序的欄位
    /// - Returns: 排序後的資料
    public func getObject(type: RealmSwift.Object.Type, predicate: NSPredicate, sortedKey: String) -> RealmSwift.Results<RealmSwift.Object>? {
        return getObject(type: type, predicate: predicate)?.sorted(byKeyPath: sortedKey)
    }
複製程式碼

集合

Realm 擁有許多能夠表示一組物件的型別,稱之為 “Realm 集合”:

  • Results 類,表示queries所返回的物件集合。
  • List 類,表示模型之間的對多關係。
  • LinkingObjects 類,表示模型之間的雙向關係。
  • RealmCollection 協議,定義了所有 Realm 集合的常用介面。
  • AnyRealmCollection 類,這是一個無型別的類,可以將呼叫轉發給具體的 Realm 集合,例如 Results、List 或者 LinkingObjects。

Realm 集合型別均實現了 RealmCollection 協議,這確保 它們的行為均保持一致。這個協議繼承自 CollectionType,因此它的使用方式 與標準庫內的集合相同。這個協議也同樣宣告瞭其他常用的 Realm 集合 API, 比如說檢索、排序、聚合操作等等。List 還存在一些額外的修改操作, 這些操作沒有在協議介面中定義,比如說新增或者刪除物件。

使用 RealmCollection 協議, 您可以編寫能夠對任意 Realm 集合進行操作的泛型程式碼:

Copy to clipboardfunc operateOn<C: RealmCollection>(collection: C) {
    // collection 既可以是 RLMResults,也可以是 RLMArray
    print("operating on collection containing \(collection.count) objects")
}
複製程式碼

由於 Swift 型別系統的限制,必須使用諸如 AnyRealmCollection 之類的無型別封裝器,才能將這個集合儲存在屬性或者變數中:

Copy to clipboardclass ViewController {
//    let collection: RealmCollection
//                    ^
//                    error: protocol 'RealmCollection' can only be used
//                    as a generic constraint because it has Self or
//                    associated type requirements
//
//    init<C: RealmCollection>(collection: C) where C.ElementType == MyModel {
//        self.collection = collection
//    }

    let collection: AnyRealmCollection<MyModel>

    init<C: RealmCollection>(collection: C) where C.ElementType == MyModel {
        self.collection = AnyRealmCollection(collection)
    }
}
複製程式碼

Realm的基礎使用先寫到這裡,更詳細的可以直接看文件(真的很詳細)。

在使用RealmSwift增刪改查又用RxSwift封裝了一層。
專案地址

參考資料

  1. 英文文件
  2. 中文文件

謝謝

相關文章