去年在個人的兩個小項(xiàng)目中嘗試著用了realm,當(dāng)時感覺簡直就時我的最愛馆铁,尤其是像我這種本身對sql不是特別熟悉的人來說榨为,以前在公司的項(xiàng)目中使用數(shù)據(jù)庫的時候,覺得太變態(tài)了啃勉,當(dāng)時公司項(xiàng)目設(shè)計了一個專門用來管理數(shù)據(jù)庫的類忽舟,一開始不是用的fmdb,而是直接使用原生的sql3的動態(tài)庫淮阐,項(xiàng)目做完后叮阅,這個數(shù)據(jù)庫管理類里面寫滿了sql的代碼,有的時候?qū)?shù)據(jù)庫做些小的更改整個人都感覺不好了
前言
Realm是由Y Combinator孵化的創(chuàng)業(yè)團(tuán)隊(duì)開源出來的一款可以用于iOS(同樣適用于Swift&Objective-C)和Android的跨平臺移動數(shù)據(jù)庫泣特。目前最新版是Realm 3.7.4浩姥,支持的平臺包括Java,Objective-C状您,Swift勒叠,React Native,Xamarin膏孟。
環(huán)境要求
- Xcode 8.3.3 or higher
- Target of iOS 8 or higher, macOS 10.9 or higher, or any version of tvOS or watchOS
安裝
Dynamic Framework
下載最新的最新的Realm發(fā)行版本眯分,并解壓
前往Xcode 工程的”General”設(shè)置項(xiàng)中,從
ios/dynamic/
,osx/
,tvos/
或者watchos/
中將Realm.framework
拖曳到”Embedded Binaries”選項(xiàng)中柒桑。確認(rèn)Copy items if needed被選中后弊决,點(diǎn)擊Finish按鈕在單元測試 Target 的”Build Settings”中,在”Framework Search Paths”中添加Realm.framework的上級目錄幕垦;
如果希望使用 Swift 加載 Realm丢氢,請拖動Swift/RLMSupport.swift文件到 Xcode 工程的文件導(dǎo)航欄中并選中Copy items if needed;
如果在 iOS先改、watchOS 或者 tvOS 項(xiàng)目中使用 Realm疚察,請?jiān)谀鷳?yīng)用目標(biāo)的”Build Phases”中,創(chuàng)建一個新的”Run Script Phase”仇奶,并將
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"
這條腳本復(fù)制到文本框中貌嫡。 因?yàn)橐@過APP商店提交的bug比驻,這一步在打包通用設(shè)備的二進(jìn)制發(fā)布版本時是必須的。
CocoaPods
在項(xiàng)目的Podfile中岛抄,添加pod 'Realm'别惦,在終端運(yùn)行pod install
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è)計我們想要存儲的屬性即可。
關(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)大的功能氯哮,包括排序际跪、查找等等操作。
開始
1.創(chuàng)建數(shù)據(jù)庫
- (void)creatDataBaseWithName:(NSString *)databaseName
{
NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [docPath objectAtIndex:0];
NSString *filePath = [path stringByAppendingPathComponent:databaseName];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.fileURL = [NSURL URLWithString:filePath];
config.readOnly = NO;
int currentVersion = 1.0;
config.schemaVersion = currentVersion;
config.migrationBlock = ^(RLMMigration *migration , uint64_t oldSchemaVersion) {
if (oldSchemaVersion < currentVersion) {
// 這里是設(shè)置數(shù)據(jù)遷移的block
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
}
2.建表
Realm數(shù)據(jù)模型是基于標(biāo)準(zhǔn) Objective?C 類來進(jìn)行定義的,使用屬性來完成模型的具體定義姆打。
我們只需要繼承 RLMObject或者一個已經(jīng)存在的模型類良姆,您就可以創(chuàng)建一個新的 Realm 數(shù)據(jù)模型對象。對應(yīng)在數(shù)據(jù)庫里面就是一張表幔戏。
#import <Realm/Realm.h>
#import <Foundation/Foundation.h>
@interface Test : RLMObject
@end
RLM_ARRAY_TYPE(Test)
@interface HTMainAccountsSubModel : RLMObject
@property NSString * subTitle;//輔助密碼名稱 ******* 必須不為空
@property NSString * password;//輔助密碼
@property (readonly) RLMLinkingObjects *owners;
@property Test * test;
@end
RLM_ARRAY_TYPE(HTMainAccountsSubModel)
@interface HTMainAccountsModel : RLMObject
@property NSString * k_push_id; //分類跳轉(zhuǎn)使用的id 默認(rèn)為@"acount"
@property NSString * k_id; //分類id ******* 必須不為空 默認(rèn)為 @"0"
@property NSString * a_id; //賬號id ******* 必須不為空
@property NSString * accountTitle; //標(biāo)題 ******* 必須不為空
@property NSString * creatTime; //創(chuàng)建時間
@property NSString * changeTime; //修改時間
@property NSString * account; //賬號 可以為空
@property NSString * password; //密碼 可以為空
@property NSString * remarks; //備注 可以為空
@property BOOL isCollect; //是否被收藏
@property NSInteger iconType; //icon類型,實(shí)際上只是為了取圖標(biāo)使用 默認(rèn)為0;
@property RLMArray<HTMainAccountsSubModel> *infoPassWord;//輔助密碼信息 例如:交易密碼,其它非登錄密碼信息
@end
RLM_ARRAY_TYPE(HTMainAccountsModel)
@implementation HTMainAccountsSubModel
//一般來說,屬性為nil的話realm會拋出異常,但是如果實(shí)現(xiàn)了這個方法的話,就只有subTitle為nil會拋出異常,也就是說現(xiàn)在其他屬性可以為空了
+ (NSArray *)requiredProperties {
return @[@"subTitle"];
}
//設(shè)置屬性默認(rèn)值
+ (NSDictionary *)defaultPropertyValues{
return @{@"subTitle":@"密碼"
};
}
//鏈接反向關(guān)系
+ (NSDictionary *)linkingObjectsProperties {
return @{
@"owners": [RLMPropertyDescriptor descriptorWithClass:HTMainAccountsModel.class propertyName:@"infoPassWord"],
};
}
@end
@implementation HTMainAccountsModel
//一般來說,屬性為nil的話realm會拋出異常,但是如果實(shí)現(xiàn)了這個方法的話,就只有accountTitle等等為nil會拋出異常,也就是說現(xiàn)在其他屬性可以為空了
+ (NSArray *)requiredProperties {
return @[@"accountTitle",
@"k_push_id",
@"a_id",
@"k_id"];
}
// 主鍵
+ (NSString *)primaryKey {
return @"a_id";
}
//設(shè)置屬性默認(rèn)值
+ (NSDictionary *)defaultPropertyValues{
return @{@"isCollect":@(NO),
@"iconType":@(0),
@"k_push_id":@"acount",
@"k_id":@"0"
};
}
@end
還可以給RLMObject設(shè)置主鍵primaryKey玛追,默認(rèn)值defaultPropertyValues,忽略的屬性ignoredProperties闲延,必要屬性requiredProperties痊剖,索引indexedProperties。比較有用的是主鍵和索引
注意慨代,RLMObject 官方建議不要加上 Objective-C的property attributes(如nonatomic, atomic, strong, copy, weak 等等)假如設(shè)置了邢笙,這些attributes會一直生效直到RLMObject被寫入realm數(shù)據(jù)庫。
RLM_ARRAY_TYPE宏創(chuàng)建了一個協(xié)議侍匙,從而允許RLMArray<HTMainAccountsSubModel>
語法的使用氮惯。如果該宏沒有放置在模型接口的底部的話,您或許需要提前聲明該模型類想暗。
-
關(guān)于RLMObject的的關(guān)系
- 對一(To-One)關(guān)系
對于多對一(many-to-one)或者一對一(one-to-one)關(guān)系來說妇汗,只需要聲明一個RLMObject子類類型的屬性即可,如上面代碼例子说莫,@property Test * test;
- 對多(To-Many)關(guān)系
通過 RLMArray類型的屬性您可以定義一個對多關(guān)系杨箭。如上面代碼例子,@property RLMArray<HTMainAccountsSubModel> *infoPassWord;
- 反向關(guān)系(Inverse Relationship)
鏈接是單向性的储狭。 因此互婿,如果對多關(guān)系屬性 HTMainAccountsModel.infoPassWord鏈接了一個 HTMainAccountsSubModel實(shí)例, 而這個實(shí)例的對一關(guān)系屬性 HTMainAccountsSubModel.owner又鏈接到了對應(yīng)的這個 HTMainAccountsModel實(shí)例辽狈, 那么實(shí)際上這些鏈接仍然是互相獨(dú)立的慈参。
//反向關(guān)系 + (NSDictionary *)linkingObjectsProperties { return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:HTMainAccountsModel.class propertyName:@"infoPassWord"], };
}
```
3.創(chuàng)建對象
// (1) 創(chuàng)建一個HTMainAccountsModel對象,然后設(shè)置其屬性
HTMainAccountsModel *model = [[HTMainAccountsModel alloc] init];
model.k_push_id = @"";
model.k_id = @"";
model.a_id = @"";
model.creatTime = @"";
// (2) 通過字典創(chuàng)建HTMainAccountsModel對象
HTMainAccountsModel *model = [[HTMainAccountsModel alloc]initWithValue:@{@"k_push_id":self.item.k_push_id,@"k_id":self.item.k_id,"a_id":@(creatTime).stringValue,@"creatTime":@(creatTime).stringValue}];
// (3) 通過數(shù)組創(chuàng)建HTMainAccountsModel對象
HTMainAccountsModel *model = [[HTMainAccountsModel alloc] initWithValue:@[]];//這行完全就只是示例
4.增
//addObject
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addObject:model];
[realm commitWriteTransaction];
//addOrUpdateObject 當(dāng)realm中已經(jīng)存在這個對象則調(diào)用這個方法
//addOrUpdateObject會去先查找有沒有傳入的model相同的主鍵刮萌,如果有驮配,就更新該條數(shù)據(jù)。這里需要注意着茸,addOrUpdateObject這個方法不是增量更新壮锻,所有的值都必須有,如果有哪幾個值是null涮阔,那么就會覆蓋原來已經(jīng)有的值猜绣,這樣就會出現(xiàn)數(shù)據(jù)丟失的問題。
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addOrUpdateObject:model];
[realm commitWriteTransaction];
5.刪
[realm beginWriteTransaction];
// 刪除單條記錄
[realm deleteObject:model];
// 刪除多條記錄
[realm deleteObjects:modelResult];
// 刪除所有記錄
[realm deleteAllObjects];
[realm commitWriteTransaction];
6.改
[realm addOrUpdateObject:model];
[HTMainAccountsModel createOrUpdateInRealm:realm withValue:@{@"k_id": @1}];
7.查
在Realm中所有的查詢(包括查詢和屬性訪問)在 Realm 中都是延遲加載的敬特,只有當(dāng)屬性被訪問時途事,才能夠讀取相應(yīng)的數(shù)據(jù)验懊。
查詢結(jié)果并不是數(shù)據(jù)的拷貝:修改查詢結(jié)果(在寫入事務(wù)中)會直接修改硬盤上的數(shù)據(jù)。同樣地尸变,您可以直接通過包含在RLMResults中的RLMObject對象完成遍歷關(guān)系圖的操作义图。除非查詢結(jié)果被使用,否則檢索的執(zhí)行將會被推遲召烂。這意味著鏈接幾個不同的臨時 {RLMResults} 來進(jìn)行排序和匹配數(shù)據(jù)碱工,不會執(zhí)行額外的工作,例如處理中間狀態(tài)奏夫。
一旦檢索執(zhí)行之后怕篷,或者通知模塊被添加之后, RLMResults將隨時保持更新酗昼,接收 Realm 中廊谓,在后臺線程上執(zhí)行的檢索操作中可能所做的更改。
//從默認(rèn)數(shù)據(jù)庫查詢所有的車
RLMResults<HTMainAccountsModel *> *model = [HTMainAccountsModel allObjects];
// 使用斷言字符串查詢
RLMResults<HTMainAccountsModel *> *model = [HTMainAccountsModel objectsWhere:@"kid = '' AND accountTitle BEGINSWITH 'm'"];
// 使用 NSPredicate 查詢
NSPredicate *pred = [NSPredicate predicateWithFormat:@"kid = %@ AND accountTitle BEGINSWITH %@",@"1", @"mm"];
RLMResults *results = [HTMainAccountsModel objectsWithPredicate:pred];
// 排序
RLMResults<HTMainAccountsModel *> *model = [[HTMainAccountsModel objectsWhere:@"kid = '11' AND accountTitle BEGINSWITH 'm'"] sortedResultsUsingProperty:@"a_id" ascending:YES];
- 跨線程訪問數(shù)據(jù)庫麻削,Realm對象一定需要新建一個,自己封裝一個Realm全局實(shí)例單例是沒啥作用的
很多開發(fā)者應(yīng)該都會對Core Data和Sqlite3或者FMDB蒸痹,自己封裝一個類似Helper的單例。于是我也在這里封裝了一個單例呛哟,在新建完Realm數(shù)據(jù)庫的時候strong持有一個Realm的對象叠荠。然后之后的訪問中只需要讀取這個單例持有的Realm對象就可以拿到數(shù)據(jù)庫了。
想法是好的扫责,但是同一個Realm對象是不支持跨線程操作realm數(shù)據(jù)庫的榛鼎。
Realm 通過確保每個線程始終擁有 Realm 的一個快照,以便讓并發(fā)運(yùn)行變得十分輕松鳖孤。你可以同時有任意數(shù)目的線程訪問同一個 Realm 文件者娱,并且由于每個線程都有對應(yīng)的快照,因此線程之間絕不會產(chǎn)生影響苏揣。需要注意的一件事情就是不能讓多個線程都持有同一個 Realm 對象的 實(shí)例 肺然。如果多個線程需要訪問同一個對象,那么它們分別會獲取自己所需要的實(shí)例(否則在一個線程上發(fā)生的更改就會造成其他線程得到不完整或者不一致的數(shù)據(jù))腿准。
其實(shí)RLMRealm *realm = [RLMRealm defaultRealm]; 這句話就是獲取了當(dāng)前realm對象的一個實(shí)例,其實(shí)實(shí)現(xiàn)就是拿到單例拾碌。所以我們每次在子線程里面不要再去讀取我們自己封裝持有的realm實(shí)例了吐葱,直接調(diào)用系統(tǒng)的這個方法即可,能保證訪問不出錯校翔。
transactionWithBlock 已經(jīng)處于一個寫的事務(wù)中弟跑,事務(wù)之間不能嵌套
[realm transactionWithBlock:^{
[self.realm beginWriteTransaction];
[self convertToRLMUserWith:bhUser To:[self convertToRLMUserWith:bhUser To:nil]];
[self.realm commitWriteTransaction];
}];
建議每個model都需要設(shè)置主鍵,這樣可以方便add和update
如果能設(shè)置主鍵防症,請盡量設(shè)置主鍵孟辑,
因?yàn)檫@樣方便我們更新數(shù)據(jù)哎甲,
我們可以很方便的調(diào)用addOrUpdateObject: 或者 createOrUpdateInRealm:withValue:方法進(jìn)行更新。
這樣就不需要先根據(jù)主鍵饲嗽,查詢出數(shù)據(jù)炭玫,
然后再去更新。
有了主鍵以后貌虾,這兩步操作可以一步完成吞加。