前言
由于最近項目中在用Realm,所以把自己實踐過程中的一些心得總結分享一下。
Realm是由Y Combinator孵化的創業團隊開源出來的一款可以用于iOS(同樣適用于Swift&Objective-C)和Android的跨平臺移動數據庫。目前最新版是Realm 2.0.2,支持的平臺包括Java,Objective-C,Swift,React Native,Xamarin。
Realm官網上說了好多優點,我覺得選用Realm的最吸引人的優點就三點:
跨平臺:現在很多應用都是要兼顧iOS和Android兩個平臺同時開發。如果兩個平臺都能使用相同的數據庫,那就不用考慮內部數據的架構不同,使用Realm提供的API,可以使數據持久化層在兩個平臺上無差異化的轉換。
簡單易用:Core Data 和 SQLite 冗余、繁雜的知識和代碼足以嚇退絕大多數剛入門的開發者,而換用 Realm,則可以極大地減少學習成本,立即學會本地化存儲的方法。毫不吹噓的說,把官方最新文檔完整看一遍,就完全可以上手開發了。
可視化:Realm 還提供了一個輕量級的數據庫查看工具,在Mac Appstore 可以下載“Realm Browser”這個工具,開發者可以查看數據庫當中的內容,執行簡單的插入和刪除數據的操作。畢竟,很多時候,開發者使用數據庫的理由是因為要提供一些所謂的“知識庫”。
“Realm Browser”這個工具調試起Realm數據庫實在太好用了,強烈推薦。
[RLMRealmConfiguration defaultConfiguration].fileURL
打印出Realm 數據庫地址,然后在Finder中??G跳轉到對應路徑下,用Realm Browser打開對應的.realm文件就可以看到數據啦.
如果是使用真機調試的話“Xcode->Window->Devices(??2)”,然后找到對應的設備與項目,點擊Download Container,導出xcappdata文件后,顯示包內容,進到AppData->Documents,使用Realm Browser打開.realm文件即可.
自2012年起, Realm 就已經開始被用于正式的商業產品中了。經過4年的使用,逐步趨于穩定。
Realm 安裝
使用 Realm 構建應用的基本要求:
- iOS 7 及其以上版本, macOS 10.9 及其以上版本,此外 Realm 支持 tvOS 和 watchOS 的所有版本。
- 需要使用 Xcode 7.3 或者以后的版本。
注意: 這里如果是純的OC項目,就安裝OC的Realm,如果是純的Swift項目,就安裝Swift的Realm。如果是混編項目,就需要安裝OC的Realm,然后要把 Swift/RLMSupport.swift 文件一同編譯進去。
RLMSupport.swift這個文件為 Objective-C 版本的 Realm 集合類型中引入了 Sequence 一致性,并且重新暴露了一些不能夠從 Swift 中進行原生訪問的 Objective-C 方法,例如可變參數 (variadic arguments)。更加詳細的說明見官方文檔。
推薦的安裝方法:
- CocoaPods,在項目的Podfile中,添加pod 'Realm',在終端運行pod install。
- Static Framework
- 下載 Realm 的最新版本并解壓,將 Realm.framework 從 ios/static/文件夾拖曳到您 Xcode 項目中的文件導航器當中。確保 Copy items if needed 選中然后單擊 Finish;
- 在 Xcode 文件導航器中選擇您的項目,然后選擇您的應用目標,進入到 Build Phases 選項卡中。在 Link Binary with Libraries 中單擊 + 號然后添加libc++.dylib;
Realm 中的相關術語
為了能更好的理解Realm的使用,先介紹一下涉及到的相關術語。
RLMRealm:Realm是框架的核心所在,是我們構建數據庫的訪問點,就如同Core Data的管理對象上下文(managed object context)一樣。出于簡單起見,realm提供了一個默認的defaultRealm( )的便利構造器方法。
RLMObject:這是我們自定義的Realm數據模型。創建數據模型的行為對應的就是數據庫的結構。要創建一個數據模型,我們只需要繼承RLMObject,然后設計我們想要存儲的屬性即可。
關系(Relationships):通過簡單地在數據模型中聲明一個RLMObject類型的屬性,我們就可以創建一個“一對多”的對象關系。同樣地,我們還可以創建“多對一”和“多對多”的關系。
寫操作事務(Write Transactions):數據庫中的所有操作,比如創建、編輯,或者刪除對象,都必須在事務中完成。“事務”是指位于write閉包內的代碼段。
查詢(Queries):要在數據庫中檢索信息,我們需要用到“檢索”操作。檢索最簡單的形式是對Realm( )數據庫發送查詢消息。如果需要檢索更復雜的數據,那么還可以使用斷言(predicates)、復合查詢以及結果排序等等操作。
RLMResults:這個類是執行任何查詢請求后所返回的類,其中包含了一系列的RLMObject對象。RLMResults和NSArray類似,我們可以用下標語法來對其進行訪問,并且還可以決定它們之間的關系。不僅如此,它還擁有許多更強大的功能,包括排序、查找等等操作。
Realm如何使用
由于Realm的API極為友好,一看就懂,所以這里就按照平時開發的順序,把需要用到的都梳理一遍。
- 創建數據庫
一般地,我們使用的為默認的Realm數據庫,即調用[RLMRealm defaultRealm]來初始化以及訪問我們的realm變量。這個方法將會返回一個 RLMRealm對象,并指向您應用的 Documents (iOS) 文件夾下的一個名為“default.realm”的文件。
用自己創建的數據庫
-(void)creatDataBaseWithName:(NSString *)databaseName {
NSArray *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [docPath objectAtIndex:0];
NSString *filePath = [path stringByAppendingPathComponent:databaseName];
NSLog(@"數據庫目錄 = %@",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) {
// 這里是設置數據遷移的block
if (oldSchemaVersion < currentVersion) {
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
}
拓展:
// 查詢指定的 Realm 數據庫
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 獲得一個指定的 Realm 數據庫
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 從該 Realm 數據庫中,檢索所有狗狗
創建數據庫主要設置RLMRealmConfiguration,設置數據庫名字和存儲地方。把路徑以及數據庫名字拼接好字符串,賦值給fileURL即可。
objectClasses這個屬性是用來控制對哪個類能夠存儲在指定 Realm 數據庫中做出限制。例如,如果有兩個團隊分別負責開發您應用中的不同部分,并且同時在應用內部使用了 Realm 數據庫,那么您肯定不希望為它們協調進行數據遷移您可以通過設置RLMRealmConfiguration的 objectClasses屬性來對類做出限制。objectClasses一般可以不用設置。
readOnly是控制是否只讀屬性。
2.建表
- 創建簡單數據模型
#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)
可以設置配置
//主鍵
+ (NSString *)primaryKey {
return @"countryId";
}
////設置屬性默認值
//+ (NSDictionary *)defaultPropertyValues {
// return @{@"dialCode":@"00" };
//}
//設置忽略屬性,即不存到realm數據庫中
+ (NSArray<NSString *> *)ignoredProperties {
return @[@"country"];
}
//一般來說,屬性為nil的話realm會拋出異常,但是如果實現了這個方法的話,就只有countryId為nil會拋出異常,也就是說現在dialCode屬性可以為空了
+ (NSArray *)requiredProperties {
return @[@"countryId"];
}
//設置索引,可以加快檢索的速度
+ (NSArray *)indexedProperties {
return @[@"countryId"];
}
- 創建嵌套數據模型
#import <Realm/Realm.h>
@class Person;
// 狗狗的數據模型
@interface Dog : RLMObject
@property NSString *name;
@property Person *owner;
@end
RLM_ARRAY_TYPE(Dog) // 定義RLMArray<Dog>
// 狗狗主人的數據模型
@interface Person : RLMObject
@property NSString *name;
@property NSDate *birthdate;
// 通過RLMArray建立關系
@property RLMArray<Dog> *dogs;
@end
RLM_ARRAY_TYPE(Person) // 定義RLMArray<Person>
注意:RLMObject 官方建議不要加上 Objective-C的property attributes(如nonatomic, atomic, strong, copy, weak 等等)假如設置了,這些attributes會一直生效直到RLMObject被寫入realm數據庫。
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進行數據管理的方式:
方式一:
RLMRealm *realm = [RLMRealm defaultRealm];
// 開放RLMRealm事務
[realm beginWriteTransaction];
// 在開放開放/提交事務之間進行數據處理
// 提交事務
[realm commitWriteTransaction];
方式二:
[realm transactionWithBlock:^{
// 進行數據處理
}];
4.刪
清空所有數據
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];
清空某條數據
RLMRealm *realm = [RLMRealm defaultRealm];
RLMResults<MJCoutryModel *> *walletResults = [MJCoutryModel allObjects];
MJCoutryModel *wallet1 = [walletResults firstObject];
[realm beginWriteTransaction];
[realm deleteObject:wallet1];
[realm commitWriteTransaction];
5.查
數據庫查詢
// 查詢默認的 Realm 數據庫
RLMResults *dogs = [Dog allObjects]; // 從默認的 Realm 數據庫中,檢索所有狗狗
如果有需要,也可以查詢指定的數據庫
// 查詢指定的 Realm 數據庫
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 獲得一個指定的 Realm 數據庫
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 從該 Realm 數據庫中,檢索所有狗狗
條件查詢
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.鏈式查詢
如果我們想獲得獲得棕黃色狗狗的查詢結果,并且在這個查詢結果的基礎上再獲得名字以“大”開頭的棕黃色狗狗。
RLMResults *tanDogs = [Dog objectsWhere:@"color = '棕黃色'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH '大'"];
6.改
- 當沒有主鍵的情況下,需要先查詢,再修改數據。
// 先查詢
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];
}
- 當有主鍵的情況下,有以下幾個非常好用的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相同的主鍵,如果有,就更新該條數據。這里需要注意,addOrUpdateObject這個方法不是增量更新,所有的值都必須有,如果有哪幾個值是null,那么就會覆蓋原來已經有的值,這樣就會出現數據丟失的問題。
createOrUpdateInRealm:withValue:這個方法是增量更新的,后面傳一個字典,使用這個方法的前提是有主鍵。方法會先去主鍵里面找有沒有字典里面傳入的主鍵的記錄,如果有,就只更新字典里面的子集。如果沒有,就新建一條記錄。
數據遷移
當您使用任意一個數據庫時,您隨時都可能打算修改您的數據模型。通過設置 RLMRealmConfiguration.schemaVersion 以及RLMRealmConfiguration.migrationBlock 可以定義一個遷移操作以及與之關聯的架構版本。 遷移閉包將會提供提供相應的邏輯操作,以讓數據模型從之前的架構轉換到新的架構中來。 每當通過配置創建完一個 RLMRealm 之后,遷移閉包將會在遷移需要的時候,將給定的架構版本應用到更新 RLMRealm 操作中。
如下所示是最簡單的數據遷移的必需流程:
// 在 [AppDelegate didFinishLaunchingWithOptions:] 中進行配置
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) {
// 只有當 Realm 數據庫的架構版本為 0 的時候,才添加 “fullName” 屬性
if (oldSchemaVersion < 1) {
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]];
}
// 只有當 Realm 數據庫的架構版本為 0 或者 1 的時候,才添加“email”屬性
if (oldSchemaVersion < 2) {
newObject[@"email"] = @"";
}
// 替換屬性名
if (oldSchemaVersion < 3) { // 重命名操作應該在調用 `enumerateObjects:` 之外完成
[migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"]; }
}];
};
[RLMRealmConfiguration setDefaultConfiguration:config];
// 現在我們已經成功更新了架構版本并且提供了遷移閉包,打開舊有的 Realm 數據庫會自動執行此數據遷移,然后成功進行訪問
[RLMRealm defaultRealm];
參考鏈接