前言
由于最近項(xiàng)目中在用Realm箱蟆,所以把自己實(shí)踐過程中的一些心得總結(jié)分享一下。
Realm是由Y Combinator孵化的創(chuàng)業(yè)團(tuán)隊(duì)開源出來的一款可以用于iOS(同樣適用于Swift&Objective-C)和Android的跨平臺移動數(shù)據(jù)庫。目前最新版是Realm 2.0.2,支持的平臺包括Java,Objective-C丽旅,Swift,React Native纺棺,Xamarin榄笙。
Realm官網(wǎng)上說了好多優(yōu)點(diǎn),我覺得選用Realm的最吸引人的優(yōu)點(diǎn)就三點(diǎn):
跨平臺:現(xiàn)在很多應(yīng)用都是要兼顧iOS和Android兩個平臺同時開發(fā)祷蝌。如果兩個平臺都能使用相同的數(shù)據(jù)庫茅撞,那就不用考慮內(nèi)部數(shù)據(jù)的架構(gòu)不同,使用Realm提供的API巨朦,可以使數(shù)據(jù)持久化層在兩個平臺上無差異化的轉(zhuǎn)換米丘。
簡單易用:Core Data 和 SQLite 冗余、繁雜的知識和代碼足以嚇退絕大多數(shù)剛?cè)腴T的開發(fā)者糊啡,而換用 Realm拄查,則可以極大地減少學(xué)習(xí)成本,立即學(xué)會本地化存儲的方法棚蓄。毫不吹噓的說堕扶,把官方最新文檔完整看一遍,就完全可以上手開發(fā)了梭依。
可視化:Realm 還提供了一個輕量級的數(shù)據(jù)庫查看工具稍算,在Mac Appstore 可以下載“Realm Browser”這個工具,開發(fā)者可以查看數(shù)據(jù)庫當(dāng)中的內(nèi)容役拴,執(zhí)行簡單的插入和刪除數(shù)據(jù)的操作糊探。畢竟,很多時候河闰,開發(fā)者使用數(shù)據(jù)庫的理由是因?yàn)橐峁┮恍┧^的“知識庫”科平。
“Realm Browser”這個工具調(diào)試起Realm數(shù)據(jù)庫實(shí)在太好用了,強(qiáng)烈推薦姜性。
[RLMRealmConfiguration defaultConfiguration].fileURL
打印出Realm 數(shù)據(jù)庫地址,然后在Finder中??G跳轉(zhuǎn)到對應(yīng)路徑下,用Realm Browser打開對應(yīng)的.realm文件就可以看到數(shù)據(jù)啦.
如果是使用真機(jī)調(diào)試的話“Xcode->Window->Devices(??2)”,然后找到對應(yīng)的設(shè)備與項(xiàng)目,點(diǎn)擊Download Container匠抗,導(dǎo)出xcappdata文件后,顯示包內(nèi)容,進(jìn)到AppData->Documents,使用Realm Browser打開.realm文件即可.
自2012年起, Realm 就已經(jīng)開始被用于正式的商業(yè)產(chǎn)品中了污抬。經(jīng)過4年的使用,逐步趨于穩(wěn)定。
Realm 安裝
使用 Realm 構(gòu)建應(yīng)用的基本要求:
- iOS 7 及其以上版本, macOS 10.9 及其以上版本印机,此外 Realm 支持 tvOS 和 watchOS 的所有版本矢腻。
- 需要使用 Xcode 7.3 或者以后的版本。
注意: 這里如果是純的OC項(xiàng)目射赛,就安裝OC的Realm多柑,如果是純的Swift項(xiàng)目,就安裝Swift的Realm楣责。如果是混編項(xiàng)目竣灌,就需要安裝OC的Realm,然后要把 Swift/RLMSupport.swift 文件一同編譯進(jìn)去秆麸。
RLMSupport.swift這個文件為 Objective-C 版本的 Realm 集合類型中引入了 Sequence 一致性初嘹,并且重新暴露了一些不能夠從 Swift 中進(jìn)行原生訪問的 Objective-C 方法,例如可變參數(shù) (variadic arguments)沮趣。更加詳細(xì)的說明見官方文檔屯烦。
推薦的安裝方法:
- CocoaPods,在項(xiàng)目的Podfile中房铭,添加pod 'Realm'驻龟,在終端運(yùn)行pod install。
- Static Framework
- 下載 Realm 的最新版本并解壓缸匪,將 Realm.framework 從 ios/static/文件夾拖曳到您 Xcode 項(xiàng)目中的文件導(dǎo)航器當(dāng)中翁狐。確保 Copy items if needed 選中然后單擊 Finish;
- 在 Xcode 文件導(dǎo)航器中選擇您的項(xiàng)目凌蔬,然后選擇您的應(yīng)用目標(biāo)露懒,進(jìn)入到 Build Phases 選項(xiàng)卡中。在 Link Binary with Libraries 中單擊 + 號然后添加libc++.dylib龟梦;
Realm 中的相關(guān)術(shù)語
為了能更好的理解Realm的使用隐锭,先介紹一下涉及到的相關(guān)術(shù)語。
RLMRealm:Realm是框架的核心所在计贰,是我們構(gòu)建數(shù)據(jù)庫的訪問點(diǎn)钦睡,就如同Core Data的管理對象上下文(managed object context)一樣。出于簡單起見躁倒,realm提供了一個默認(rèn)的defaultRealm( )的便利構(gòu)造器方法荞怒。
RLMObject:這是我們自定義的Realm數(shù)據(jù)模型。創(chuàng)建數(shù)據(jù)模型的行為對應(yīng)的就是數(shù)據(jù)庫的結(jié)構(gòu)秧秉。要創(chuàng)建一個數(shù)據(jù)模型褐桌,我們只需要繼承RLMObject,然后設(shè)計(jì)我們想要存儲的屬性即可象迎。
關(guān)系(Relationships):通過簡單地在數(shù)據(jù)模型中聲明一個RLMObject類型的屬性荧嵌,我們就可以創(chuàng)建一個“一對多”的對象關(guān)系呛踊。同樣地,我們還可以創(chuàng)建“多對一”和“多對多”的關(guān)系啦撮。
寫操作事務(wù)(Write Transactions):數(shù)據(jù)庫中的所有操作谭网,比如創(chuàng)建、編輯赃春,或者刪除對象愉择,都必須在事務(wù)中完成≈校“事務(wù)”是指位于write閉包內(nèi)的代碼段锥涕。
查詢(Queries):要在數(shù)據(jù)庫中檢索信息,我們需要用到“檢索”操作狭吼。檢索最簡單的形式是對Realm( )數(shù)據(jù)庫發(fā)送查詢消息层坠。如果需要檢索更復(fù)雜的數(shù)據(jù),那么還可以使用斷言(predicates)搏嗡、復(fù)合查詢以及結(jié)果排序等等操作窿春。
RLMResults:這個類是執(zhí)行任何查詢請求后所返回的類,其中包含了一系列的RLMObject對象采盒。RLMResults和NSArray類似旧乞,我們可以用下標(biāo)語法來對其進(jìn)行訪問,并且還可以決定它們之間的關(guān)系磅氨。不僅如此尺栖,它還擁有許多更強(qiáng)大的功能,包括排序烦租、查找等等操作延赌。
Realm如何使用
由于Realm的API極為友好,一看就懂叉橱,所以這里就按照平時開發(fā)的順序挫以,把需要用到的都梳理一遍。
- 創(chuàng)建數(shù)據(jù)庫
一般地,我們使用的為默認(rèn)的Realm數(shù)據(jù)庫,即調(diào)用[RLMRealm defaultRealm]來初始化以及訪問我們的realm變量窃祝。這個方法將會返回一個 RLMRealm對象掐松,并指向您應(yīng)用的 Documents (iOS) 文件夾下的一個名為“default.realm”的文件。
用自己創(chuàng)建的數(shù)據(jù)庫
-(void)creatDataBaseWithName:(NSString *)databaseName {
NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [docPath objectAtIndex:0];
NSString *filePath = [path stringByAppendingPathComponent:databaseName];
NSLog(@"數(shù)據(jù)庫目錄 = %@",filePath);
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [NSURL URLWithString:filePath];
config.objectClasses = @[MyClass.class, MyOtherClass.class];
config.readOnly = NO;
int currentVersion = 1.0;
config.schemaVersion = currentVersion;
config.migrationBlock = ^(RLMMigration *migration , uint64_t oldSchemaVersion) {
// 這里是設(shè)置數(shù)據(jù)遷移的block
if (oldSchemaVersion < currentVersion) {
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
}
拓展:
// 查詢指定的 Realm 數(shù)據(jù)庫
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 獲得一個指定的 Realm 數(shù)據(jù)庫
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 從該 Realm 數(shù)據(jù)庫中粪小,檢索所有狗狗
創(chuàng)建數(shù)據(jù)庫主要設(shè)置RLMRealmConfiguration大磺,設(shè)置數(shù)據(jù)庫名字和存儲地方。把路徑以及數(shù)據(jù)庫名字拼接好字符串探膊,賦值給fileURL即可杠愧。
objectClasses這個屬性是用來控制對哪個類能夠存儲在指定 Realm 數(shù)據(jù)庫中做出限制。例如逞壁,如果有兩個團(tuán)隊(duì)分別負(fù)責(zé)開發(fā)您應(yīng)用中的不同部分流济,并且同時在應(yīng)用內(nèi)部使用了 Realm 數(shù)據(jù)庫锐锣,那么您肯定不希望為它們協(xié)調(diào)進(jìn)行數(shù)據(jù)遷移您可以通過設(shè)置RLMRealmConfiguration的 objectClasses屬性來對類做出限制。objectClasses一般可以不用設(shè)置袭灯。
readOnly是控制是否只讀屬性刺下。
2.建表
- 創(chuàng)建簡單數(shù)據(jù)模型
#import <Realm/Realm.h>
@interface MJCoutryModel : RLMObject
@property (nonatomic, copy) NSString *countryId;
@property (nonatomic, copy) NSString *country;
@property (nonatomic, copy) NSString *dialCode;
@end
RLM_ARRAY_TYPE(MJCoutryModel)
可以設(shè)置配置
//主鍵
+ (NSString *)primaryKey {
return @"countryId";
}
////設(shè)置屬性默認(rèn)值
//+ (NSDictionary *)defaultPropertyValues {
// return @{@"dialCode":@"00" };
//}
//設(shè)置忽略屬性,即不存到realm數(shù)據(jù)庫中
+ (NSArray<NSString *> *)ignoredProperties {
return @[@"country"];
}
//一般來說,屬性為nil的話realm會拋出異常,但是如果實(shí)現(xiàn)了這個方法的話,就只有countryId為nil會拋出異常,也就是說現(xiàn)在dialCode屬性可以為空了
+ (NSArray *)requiredProperties {
return @[@"countryId"];
}
//設(shè)置索引,可以加快檢索的速度
+ (NSArray *)indexedProperties {
return @[@"countryId"];
}
- 創(chuàng)建嵌套數(shù)據(jù)模型
#import <Realm/Realm.h>
@class Person;
// 狗狗的數(shù)據(jù)模型
@interface Dog : RLMObject
@property NSString *name;
@property Person *owner;
@end
RLM_ARRAY_TYPE(Dog) // 定義RLMArray<Dog>
// 狗狗主人的數(shù)據(jù)模型
@interface Person : RLMObject
@property NSString *name;
@property NSDate *birthdate;
// 通過RLMArray建立關(guān)系
@property RLMArray<Dog> *dogs;
@end
RLM_ARRAY_TYPE(Person) // 定義RLMArray<Person>
注意:RLMObject 官方建議不要加上 Objective-C的property attributes(如nonatomic, atomic, strong, copy, weak 等等)假如設(shè)置了,這些attributes會一直生效直到RLMObject被寫入realm數(shù)據(jù)庫稽荧。
3.增
Dog *myDog = [[Dog alloc] init];
myDog.name = @"小花";
Dog *yourDog = [[Dog alloc] init];
yourDog.name = @"小黑";
Person *me = [[Person alloc] initWithValue:@[@"小明",[NSDate dateWithTimeIntervalSinceNow:1],@[myDog,yourDog]]];
yourDog.owner = me;
myDog.owner = me;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addObject:yourDog];
[realm addObject:myDog];
[realm addObject:me];
[realm commitWriteTransaction];
使用Realm進(jìn)行數(shù)據(jù)管理的方式:
方式一:
RLMRealm *realm = [RLMRealm defaultRealm];
// 開放RLMRealm事務(wù)
[realm beginWriteTransaction];
// 在開放開放/提交事務(wù)之間進(jìn)行數(shù)據(jù)處理
// 提交事務(wù)
[realm commitWriteTransaction];
方式二:
[realm transactionWithBlock:^{
// 進(jìn)行數(shù)據(jù)處理
}];
4.刪
清空所有數(shù)據(jù)
RLMRealm *realm = [RLMRealm defaultRealm];
RLMResults<Person *> *personResults = [Person allObjects];
if (personResults.count > 0) {
[realm beginWriteTransaction];
[realm deleteObjects:personResults];
[realm commitWriteTransaction];
}
RLMResults<Dog *> *dogResults = [Dog allObjects];
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransaction];
清空某條數(shù)據(jù)
RLMRealm *realm = [RLMRealm defaultRealm];
RLMResults<MJCoutryModel *> *walletResults = [MJCoutryModel allObjects];
MJCoutryModel *wallet1 = [walletResults firstObject];
[realm beginWriteTransaction];
[realm deleteObject:wallet1];
[realm commitWriteTransaction];
5.查
數(shù)據(jù)庫查詢
// 查詢默認(rèn)的 Realm 數(shù)據(jù)庫
RLMResults *dogs = [Dog allObjects]; // 從默認(rèn)的 Realm 數(shù)據(jù)庫中,檢索所有狗狗
如果有需要,也可以查詢指定的數(shù)據(jù)庫
// 查詢指定的 Realm 數(shù)據(jù)庫
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 獲得一個指定的 Realm 數(shù)據(jù)庫
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 從該 Realm 數(shù)據(jù)庫中工腋,檢索所有狗狗
條件查詢
1.使用斷言字符串查詢:
RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黃色' AND name BEGINSWITH '大'" ascending:YES];
2.使用 NSPredicate 查詢
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
@"棕黃色", @"大"];
RLMResults *tanDogs = [Dog objectsWithPredicate:pred];
3.鏈?zhǔn)讲樵?br> 如果我們想獲得獲得棕黃色狗狗的查詢結(jié)果姨丈,并且在這個查詢結(jié)果的基礎(chǔ)上再獲得名字以“大”開頭的棕黃色狗狗。
RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黃色'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH '大'"];
6.改
- 當(dāng)沒有主鍵的情況下擅腰,需要先查詢蟋恬,再修改數(shù)據(jù)。
// 先查詢
RLMRealm *realm = [RLMRealm defaultRealm];
NSPredicate *pred = [NSPredicate predicateWithFormat:@"dialCode = %@ AND country BEGINSWITH %@",@"123", @"中國"];
RLMResults<MJCoutryModel *> *walletResults = [MJCoutryModel objectsWithPredicate:pred];
// 再修改
for (int i = 0; i < walletResults.count; i++) {
MJCoutryModel *wallet = walletResults[i];
[realm beginWriteTransaction];
wallet.country = @"托馬斯品欽";
[realm commitWriteTransaction];
}
- 當(dāng)有主鍵的情況下趁冈,有以下幾個非常好用的API
RLMRealm *realm = [RLMRealm defaultRealm];
MJCoutryModel *country = [[MJCoutryModel alloc] init];
country.countryId = @"21";
country.country = @"ee21";
country.dialCode = @"22";
[realm beginWriteTransaction];
// 方法一
[MJCoutryModel createOrUpdateInRealm:realm withValue:country];
// 方法二
[realm addOrUpdateObject:country];
[realm commitWriteTransaction];
addOrUpdateObject: 會去先查找有沒有傳入的Car相同的主鍵歼争,如果有,就更新該條數(shù)據(jù)渗勘。這里需要注意沐绒,addOrUpdateObject這個方法不是增量更新,所有的值都必須有旺坠,如果有哪幾個值是null乔遮,那么就會覆蓋原來已經(jīng)有的值,這樣就會出現(xiàn)數(shù)據(jù)丟失的問題取刃。
createOrUpdateInRealm:withValue:這個方法是增量更新的蹋肮,后面?zhèn)饕粋€字典,使用這個方法的前提是有主鍵璧疗。方法會先去主鍵里面找有沒有字典里面?zhèn)魅氲闹麈I的記錄坯辩,如果有,就只更新字典里面的子集崩侠。如果沒有漆魔,就新建一條記錄。
數(shù)據(jù)遷移
當(dāng)您使用任意一個數(shù)據(jù)庫時啦膜,您隨時都可能打算修改您的數(shù)據(jù)模型有送。通過設(shè)置 RLMRealmConfiguration.schemaVersion 以及RLMRealmConfiguration.migrationBlock 可以定義一個遷移操作以及與之關(guān)聯(lián)的架構(gòu)版本。 遷移閉包將會提供提供相應(yīng)的邏輯操作僧家,以讓數(shù)據(jù)模型從之前的架構(gòu)轉(zhuǎn)換到新的架構(gòu)中來雀摘。 每當(dāng)通過配置創(chuàng)建完一個 RLMRealm 之后,遷移閉包將會在遷移需要的時候八拱,將給定的架構(gòu)版本應(yīng)用到更新 RLMRealm 操作中阵赠。
如下所示是最簡單的數(shù)據(jù)遷移的必需流程:
// 在 [AppDelegate didFinishLaunchingWithOptions:] 中進(jìn)行配置
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 2;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion)
{
// enumerateObjects:block: 遍歷了存儲在 Realm 文件中的每一個“Person”對象
[migration enumerateObjects:MJCoutryModel.className block:^(RLMObject *oldObject, RLMObject *newObject) {
// 只有當(dāng) Realm 數(shù)據(jù)庫的架構(gòu)版本為 0 的時候涯塔,才添加 “fullName” 屬性
if (oldSchemaVersion < 1) {
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]];
}
// 只有當(dāng) Realm 數(shù)據(jù)庫的架構(gòu)版本為 0 或者 1 的時候,才添加“email”屬性
if (oldSchemaVersion < 2) {
newObject[@"email"] = @"";
}
// 替換屬性名
if (oldSchemaVersion < 3) { // 重命名操作應(yīng)該在調(diào)用 `enumerateObjects:` 之外完成
[migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"]; }
}];
};
[RLMRealmConfiguration setDefaultConfiguration:config];
// 現(xiàn)在我們已經(jīng)成功更新了架構(gòu)版本并且提供了遷移閉包清蚀,打開舊有的 Realm 數(shù)據(jù)庫會自動執(zhí)行此數(shù)據(jù)遷移匕荸,然后成功進(jìn)行訪問
[RLMRealm defaultRealm];
參考鏈接