由于最近公司需要將項目用
Swift
改寫,項目中需要大量使用數據庫,之前OC
使用的是Core Data
,Core Data
使用起來確實十分的繁瑣,故決定在Swift
中棄用,改用Realm
數據庫,下面將使用方法記錄下來方便以后查看。
Realm 的優點
Realm
不是基于 Core Data
,也不是基于 SQLite
封裝構建的。它有自己的數據庫存儲引擎,下面說一下 Realm
的一些優點。
跨平臺: 現在很多應用都是要兼顧
iOS
和Android
兩個平臺同時開發。如果兩個平臺都能使用相同的數據庫,那就不用考慮內部數據的架構不同,使用Realm
提供的API
,可以使數據持久化層在兩個平臺上無差異化的轉換。代碼可以使用Swift
、Objective-C
以及Java
語言來編寫。簡單易用:
Core Data
和SQLite
冗余、繁雜的知識和代碼足以嚇退絕大多數剛入門的開發者,而換用Realm
,則可以極大地減少學習成本,立即學會本地化存儲的方法。大部分常用的功能(比如插入、查詢等)都可以用一行簡單的代碼輕松完成,毫不吹噓的說,把官方最新文檔完整看一遍,就完全可以上手開發了,這是 中文官方文檔地址。可視化:
Realm
還提供了一個輕量級的數據庫查看工具,在Mac Appstore
可以下載Realm Browser
這個工具,開發者可以查看數據庫當中的內容,執行簡單的插入和刪除數據的操作。
Realm Swift 的安裝
這是 Realm
的 GitHub 地址 ,其他方法我就不說了,我是用 CocoaPods
方式安裝的,所以就只說 CocoaPods
的安裝方法了。
運行
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
文件就可以看到數據了。
2. 真機調試
- 如果是真機調試的話,打開
Xcode
,選擇菜單Window
下的Devices
。
- 選擇對應的設備與項目,點擊
Download Container
。
- 導出
xcappdata
文件后,顯示包內容,進到AppData
下的Documents
,使用Realm Browser
打開.realm
文件即可。
Realm Swift 的使用
1. 配置 Realm 數據庫
- 將以下代碼寫在
AppDelegate
的didFinishLaunchingWithOptions
方法中,這個方法主要用于數據模型屬性增加或刪除時的數據遷移,每次模型屬性變化時,將schemaVersion
加1
即可,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)")
}
}
- 如果屬性改變后,想要保留原來已存在的數據來更新新的屬性值,在屬性變化后將
schemaVersion
加1
,并將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)"
}
}
})
- 如果是只是屬性重命名,想保留原來已經存在的數據,重命名以后將
schemaVersion
加1
,并將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
支持這幾種屬性類型:Bool
、Int8
、Int16
、Int32
、Int64
、Double
、Float
、String
、NSDate
以及 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 數據模型創建
下面以 Dog
和 Person
為例,通過簡單的繼承 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
可以將 Int
和 String
類型的屬性設為主鍵,但是不支持自增長屬性,所以只能自己給主鍵生成一個唯一的標識,可以使用 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
將不會干涉這些屬性的常規操作,它們將由成員變量提供支持,并且您能夠輕易重寫它們的 setter
和 getter
。
/// 狗狗的數據模型
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) // 更新多個數據
}
-
鍵值編碼:
Object
、Result
以及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數據庫 從入門到“放棄” ,寫的非常詳細,也參考了不少這篇文章的內容。
將來的你,一定會感激現在拼命的自己,愿自己與讀者的開發之路無限美好。