PS:以下內(nèi)容均為項目為Swift的基礎(chǔ),如果需要Object-c請查閱Realm的Object-c文檔。
前些日子在看第一屆《中國Swift開發(fā)者大會》的時候,聽到了realm這個數(shù)據(jù)庫,說到了運行速度方面,相較于CoreData可以有很大的提升。再加上之前在做項目的時候,覺的coredata的很多配置讓我覺的不爽,很是繁瑣。所以就想看一看realm到底如何,學(xué)習(xí)了一天覺的很好,固決定記錄一下。因為也不知道怎么講解,所以我會嘗試翻譯realm官網(wǎng)的文檔,再加上我的理解進行解釋。PS:寫完博客之后發(fā)覺,人家有中文API…。
Realm is a mobile database
hundreds of millions of people rely on
這是Realm官網(wǎng)的對于自己的解釋。
realm可以快速有效的編寫應(yīng)用程序的本地持久化,它的代碼大概就是下面這個樣子:
// 先創(chuàng)建一個你想要持久化的對象的模型,Object 是 Reaml 自己定義的對象
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
class Person: Object {
dynamic var name = ""
dynamic var picture: NSData? = nil // 這是一個可選類型
let dogs = List<Dog>()
}
// 這是實例化一個Realm對象的時候,就像咱們平時創(chuàng)建Class對象一樣的
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
print("name of dog: \\(myDog.name)")
// 得到一個Realm對象
let realm = try! Realm()
// 查詢這個狗小于兩歲的,其實咱們看著代碼就可以理解了,它使用了鏈?zhǔn)酱a結(jié)構(gòu),很容易看懂
let puppies = realm.objects(Dog).filter("age < 2")
puppies.count // => 0 比如現(xiàn)在的到的數(shù)目是0,因為現(xiàn)在咱們還沒有添加任何數(shù)據(jù)
// 把咱們的上面創(chuàng)建的狗的對象,添加到數(shù)據(jù)庫中
try! realm.write {
realm.add(myDog)
}
// 再次查看數(shù)量的時候,不需要再進行一次查詢,Realm會自動的為你完成更新
puppies.count // => 1
// 你也可以更新你的數(shù)據(jù)內(nèi)容在任何線程
dispatch_async(dispatch_queue_create("background", nil)) {
let realm = try! Realm()
let theDog = realm.objects(Dog).filter("age == 1").first
try! realm.write {
theDog!.age = 3
}
}
是不是很方便。那么接下來咱們就從幾個簡單的方面,來完成本次Realm的教程吧。
- [項目中如何添加 Realm 框架](#項目中如何添加 Realm 框架)
- 方便快捷的創(chuàng)建Realm模型 坑?。。?/li>
- [創(chuàng)建Realm數(shù)據(jù)模型](#創(chuàng)建Realm 數(shù)據(jù)模型)
-
數(shù)據(jù)的操作
- 向Realm插入數(shù)據(jù)
-
Realm修改數(shù)據(jù)
- [Typed Updates](#Typed Updates)
- 根據(jù)數(shù)據(jù)的主鍵進行修改
- 修改大批量數(shù)據(jù)
- Realm刪除數(shù)據(jù)
- Realm查詢數(shù)據(jù)
- Realm版本遷移
-
Realm數(shù)據(jù)變化通知
<a name='項目中如何添加 Realm 框架'></a>
項目中如何添加 Realm 框架
- 使用Dynamic Framework:
- 下載Realm安裝包最新版本
- 根據(jù)你的需要,將你下載文件解壓后, 從/ios/swift-2.1.1(IOS設(shè)備) 或者 tvos/(Apple電視設(shè)備) 再或者 osx/swift-2.1.1/(mac電腦設(shè)備) 這三個目錄中,選擇自己需要的文件,將其拖入項目中,并且要選中“如果需要請勾選”選項
- 在你的項目中 Build Settings ,為Framework Search Paths 設(shè)置ealmSwift.framework 路徑
- 如果使用Realm 在 IOS,watchOS或者TvOS,在Build Phase 創(chuàng)建一個新的 “Run Script Phase”,下面寫上
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"這一步是需要解決應(yīng)用程序商店提交bug時存檔通用二進制文件。
- 使用CocoaPods
- 安裝
CocoaPods0.39.0 版本或者更新的版本. - 在你的Podfile文件中添加
use_frameworks!和pod 'RealmSwift' - 在終端中運行
pod install - 打開
*.xcworkspace項目
- 使用Carthage
- 安裝
Carthage0.9.2 或者更新的版本 - 在你的Cartfile文件中添加
"realm/realm-cocoa" - 運行
carthage update. - 從 Carthage/Build/ 拖拽RealmSwift.framework 和 Realm.framework 到 General 下面的“Embedded Binaries”
- iOS/watchOS/tvOS: 在你的項目targets的 “Build Phases”設(shè)置項中,點擊“+”好按鈕,添加一個新的“New Run Script Phase”,內(nèi)容如下
/usr/local/bin/carthage copy-frameworks和路徑添加到框架下您想要使用“輸入文件”,如$(SRCROOT)/Carthage/Build/iOS/Realm.framework,
$(SRCROOT)/Carthage/Build/iOS/RealmSwift.framework這個腳本是在應(yīng)用程序商店提交錯誤引發(fā)了普遍的二進制文件。確保這一階段后,“嵌入框架”階段。
<a name='方便快捷的創(chuàng)建Realm模型'></a>
方便快捷的創(chuàng)建Realm模型
首先需要安裝Alcatraz,在終端里面執(zhí)行以下命令.如果執(zhí)行失敗,請翻墻后在嘗試
curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/deploy/Scripts/install.sh | sh
安裝完成之后,重啟xcode就可以在菜單window選項下面看到一個Package Manager 選項,點擊 安裝package ,搜索 Xcode Plugin ,之后,再重啟。創(chuàng)建創(chuàng)建文件,就可以文件類型中有一個Realm選項,點擊創(chuàng)建。之后生成文件! 你會發(fā)現(xiàn)??!SHIT?。。。?!我不如自己寫了?。。。。。?!
<a name='創(chuàng)建Realm 數(shù)據(jù)模型'></a>
創(chuàng)建Realm 數(shù)據(jù)模型
其實創(chuàng)建Realm數(shù)據(jù)模型的方法超級簡單,就和咱們平時創(chuàng)建Class是一樣一樣的。
import RealmSwift
// 狗的數(shù)據(jù)模型
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person? // 狗的主人是可以為空的
}
但是,在們必須知道他都允許咱們創(chuàng)建什么類型的數(shù)據(jù)。
這樣子咱們創(chuàng)建數(shù)據(jù)模型的時候就沒有問題了吧。當(dāng)然Realm是支持綁定關(guān)系的。一對一,一對多之類的,都可以的。
<a name='數(shù)據(jù)的操作'></a>
數(shù)據(jù)的操作
<a name='向Realm插入數(shù)據(jù)'></a>
向Realm插入數(shù)據(jù)
終于迎來了,咱們的增刪改查了。
當(dāng)你定義一個模型你可以實例化對象子類和新實例添加到域??紤]一下這個簡單的模型:
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
我們可以以下幾個方式創(chuàng)建對象:
// (1) 先創(chuàng)建狗的對象,然后在給予其賦值
var myDog = Dog()
myDog.name = "Rex"
myDog.age = 10
// (2) 使用字典創(chuàng)建
let myOtherDog = Dog(value: ["name" : "Pluto", "age": 3])
// (3) 使用一個Array創(chuàng)建y。
let myThirdDog = Dog(value: ["Fido", 5])
1.最顯而易見的就是 直接創(chuàng)建對象,并且賦值
2.也可以使用字典實例化對象,但是!要使用數(shù)據(jù)模型中對應(yīng)的!的鍵和值。
3.最后,可以使用數(shù)組實例化對象。但是要注意!數(shù)組中的值,必須和數(shù)據(jù)模型中的屬性的順序保持一致
創(chuàng)建完成對象之后就是把它放入數(shù)據(jù)庫里面了,你可以像下面的方法一樣放進數(shù)據(jù)庫
// 創(chuàng)建一個用戶對象,并且賦值
let author = Person()
author.name = "David Foster Wallace"
// 得到默認的Realm對象
let realm = try! Realm()
// 你只需要這么做一次
// 添加這個對象到Realm
try! realm.write {
realm.add(author)
}
這樣就在數(shù)據(jù)庫中,添加了一個對象了。
<a name='Realm修改數(shù)據(jù)'></a>
Realm修改數(shù)據(jù)
Realm 提供很多方式去修改數(shù)據(jù),請選擇適合自己的方式進行修改
<a name='Typed Updates'></a>
Typed Updates
你可以修改任何對象的屬性,在write進程中
// Update an object with a transaction
try! realm.write {
author.name = "Thomas Pynchon"
}
<a name='根據(jù)數(shù)據(jù)的主鍵進行修改'></a>
根據(jù)數(shù)據(jù)的主鍵進行修改
重寫數(shù)據(jù)模型的Object.primaryKey()方法可以設(shè)置模型的主鍵,聲明一個主鍵,允許對象可以通過主鍵進行有效的查詢和修改,并強制每個值的唯一性。一旦一個對象添加到Realm,主鍵就不能更改
class Book: Object {
dynamic var id = 0
dynamic var price = 0
dynamic var title = ""
override static func primaryKey() -> String? {
return "id"
}
}
因為這個數(shù)據(jù)模型含有主鍵,所以再向Realm添加一個對象時,如果已經(jīng)存在同樣主鍵的對象時,Realm會自動更新前一個數(shù)據(jù)的其它不一樣的屬性。而如果不存在這個主鍵的對象,Realm會創(chuàng)建一個新的對象
// 假設(shè)Realm中存在一個主鍵為1的對象的時候
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1
// id==1,修改這個書的屬性
try! realm.write {
realm.add(cheeseBook, update: true)
}
你也可以通過一個字典的方式,指定主鍵和你要修改的屬性,來進行修改用戶
// 假設(shè)數(shù)據(jù)庫中已經(jīng)存在一個主鍵為1的Book時
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
// the book's `title` property will remain unchanged.
}
<a name='修改大批量數(shù)據(jù)'></a>
修改大批量數(shù)據(jù)
可以獲取一群數(shù)據(jù)后,指定這些數(shù)據(jù)其中的某些數(shù)據(jù),或者全部數(shù)據(jù)。 的某一歇屬性值。
let persons = realm.objects(Person)
try! realm.write {
// 設(shè)置第一個人的 isFirst對象為True
persons.first?.setValue(true, forKeyPath: "isFirst")
// 把每個人的所居住的行星,設(shè)置為地球
persons.setValue("Earth", forKeyPath: "planet")
}
<a name='Realm刪除數(shù)據(jù)'></a>
Realm刪除數(shù)據(jù)
當(dāng)用戶想刪除某一個對象的時候,查詢出來,之后~
// let cheeseBook = ... Book stored in Realm
//刪除一個對象,在Wirte交易中。
try! realm.write {
realm.delete(cheeseBook)
}
你也可以刪除Realm中的全部數(shù)據(jù),這很快速
// 刪除Realm中所有的對象
try! realm.write {
realm.deleteAll()
}
<a name='Realm查詢數(shù)據(jù)'></a>
Realm查詢數(shù)據(jù)
Realm稱為這么受歡迎的數(shù)據(jù)庫的原因來了!咱們看她們官方的解釋。
Queries return a Results instance, which contains a collection of Objects. Results have an interface very similar to Array and objects contained in a Results can be accessed using indexed subscripting. Unlike Arrays, Results only hold Objects of a single subclass type.
查詢返回一個結(jié)果實例,它包含一個對象集合。結(jié)果類似于Array一樣的東西,可以使用下標(biāo)來進行每一個數(shù)據(jù)的訪問。和Arrays不一樣的地方在于,查詢結(jié)果只持有一種類型的對象。
All queries (including queries and property access) are lazy in Realm. Data is only read when the properties are accessed.
在Realm中,所有的查詢(這里面包括了查詢和屬性的訪問)都是懶加載的。數(shù)據(jù)僅僅在數(shù)據(jù)訪問的時候再進行唯一的一次讀取
那么就讓我們來試試吧。
let dogs = realm.objects(Dog) // 這樣子就查詢了Realm數(shù)據(jù)庫中所有的狗
<a name='條件篩選'></a>
條件篩選
如果你熟悉nspredicate,那么你已經(jīng)到了指導(dǎo)如何查詢的境界。對象、Realms,列表,和結(jié)果都提供了方法,查詢一個結(jié)果Arrays 可以傳遞一個nspredicate實例,謂詞字符串,或謂格式字符串。
例如,檢索所有狗的顏色棕褐色和名字首“B”從默認的境界:
// 查詢使用謂詞字符串
var tanDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'")
// 查詢使用 NSPredicate 實例
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog).filter(predicate)
看到蘋果的謂詞編程指南建立謂詞的更多信息和使用我們的nspredicate列表。領(lǐng)域支持許多共同的謂詞:
- 比較操作數(shù)可以是屬性名稱或常量。至少一個操作數(shù)必須是一個屬性名。
- 比較運算符= =,<,> =,>,!=,支持
Int,Int8,Int16,Int32,Int64,Float,Double和NSDate屬性類型。如:age == 45 - 身份比較 == ,!= ,
Results<Employee>().filter("company == %@", company) - 比較運算符 = 和 != 需要
Bool屬性的支持。 - 字符串和NSData性質(zhì),我們我們支持
==,!=,BEGINSWITH,CONTAINS, andENDSWITH,如名稱中CONTAINS ‘Ja’ - 不區(qū)分大小寫的比較字符串,如:
CONTAINS[c] ‘Ja’。請注意只有字符A-Z和a-z會被忽略。 - Realm支持以下復(fù)合操作::
AND,OR, andNOT. 例如:name BEGINSWITH ‘J’ AND age >= 32 - 包含操作符號 IN, 如 名字
name IN {‘Lisa’, ‘Spike’, ‘Hachi’} - 我們可以比較屬性是否為空,如:
Results<Company>().filter("ceo == nil").注意,境界把Nil作為一個特殊的值而不是一個屬性的缺失,所以不像SQL零等于本身。 - 任何的比較,如有
student.age<21 - 聚合表達式
@count,@min,@max,@sum和@avgRealm都支持。例如:realm.objects(Company).filter("employees.@count > 5")。找到所有員工在五個以上的公司 - 子查詢的限制和支持:
-
@count在子查詢表達式是唯一的。 -
SUBQUERY(…).@count必須和一個常量進行比較 - 相關(guān)子查詢現(xiàn)在還不支持
<a name='排序'></a>
排序
結(jié)果允許您在一個或多個屬性的基礎(chǔ)上指定一個排序標(biāo)準(zhǔn)和順序。例如,下面的例子中狗按照名字來進行排序返回:
let sortedDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted("name")
<a name='鏈?zhǔn)讲樵?></a>
鏈?zhǔn)讲樵?/strong>
Realm 和其它的查區(qū)別在于,他每一個查詢結(jié)果都擁有繼續(xù)查詢的能力,這樣用戶就可以對自己查詢的結(jié)果進行進一步的詢問。例如:我們先查找了一群棕色的狗,之后又想查詢處其中名字以“B”開頭的狗。
let tanDogs = realm.objects(Dog).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")
<a name='Realm版本遷移'></a>
# Realm版本遷移
其實一般的版本遷移有兩種情況,第一種。刪除了某些字段
class Person: Object {
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var age = 0
}
To:
class Person: Object {
dynamic var fullName = ""
dynamic var age = 0
}
接下來,你將進行
// 在你的(application:didFinishLaunchingWithOptions:)
let config = Realm.Configuration(
// 設(shè)置一個新的版本,如果你從來沒有設(shè)置過版本,那么默認版本就是0
schemaVersion: 1,
/// 設(shè)置一個找到不一樣版本的回調(diào)方法
migrationBlock: { migration, oldSchemaVersion in
// 因為我們沒有設(shè)置過版本,所以 oldSchemaVersion < 1
if (oldSchemaVersion < 1) {
// 什么都不用做
// Realm 將自動檢測新的屬性和刪除的屬性
// 并會自動更新磁盤模式
}
})
// 告訴Realm 使用這個新的配置作為默認配置
Realm.Configuration.defaultConfiguration = config
// 接下來就讓我們獲得默認的對象吧
let realm = try! Realm()
而如果你添加了一些字段,比如咱們不想要FirstName和LastName了,我們希望他變成一個字段!fullName!
// 在你的(application:didFinishLaunchingWithOptions:)
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// 枚舉所有的屬性
migration.enumerate(Person.className()) { oldObject, newObject in
// 從舊的對象中獲取之前存儲的東西
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
// 設(shè)置到新的對象中的屬性上去
newObject!["fullName"] = "\\(firstName) \\(lastName)"
}
}
})
還有一種情況:從版本0 直接 升到 版本2
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 2,
migrationBlock: { migration, oldSchemaVersion in
// The enumerateObjects:block: method iterates
// over every 'Person' object stored in the Realm file
migration.enumerate(Person.className()) { oldObject, newObject in
// Add the `fullName` property only to Realms with a schema version of 0
if oldSchemaVersion < 1 {
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["fullName"] = "\\(firstName) \\(lastName)"
}
// Add the `email` property to Realms with a schema version of 0 or 1
if oldSchemaVersion < 2 {
newObject!["email"] = ""
}
}
})
// Realm will automatically perform the migration and opening the Realm will succeed
let realm = try! Realm()
<a name='Realm數(shù)據(jù)變化通知'></a>
Realm數(shù)據(jù)變化通知
結(jié)果或列表是通過調(diào)用addnotificationblock方法在其數(shù)值發(fā)生變化的時候給予通知這是可能的。
每一次寫事務(wù)提交時,在其他線程上的其他線程發(fā)送通知到其他實例:
// 注冊通知
let token = realm.addNotificationBlock { notification, realm in
viewController.updateUI()
}
// 稍后
token.stop()
當(dāng)然你也可以注冊某一范圍的數(shù)值發(fā)生變化的時候,進行給予通知
// 大于5歲的人年紀(jì)發(fā)生變化的時候進行統(tǒng)治
let token = realm.objects(Person).filter("age > 5").addNotificationBlock { results, error in
// results 就已經(jīng)是 `realm.objects(Person).filter("age > 5")`的結(jié)果集了
viewController.updateUI()
}
// 稍后
token.stop()
其它更詳細的文檔請訪問 Realm-Swift文檔