走進 Realm 的世界


 

來源:XcodeMen(郭傑)   

鏈接:http://www.jianshu.com/p/0e248f000405

 

本文由我們團隊的郭傑童鞋分享。

 


 

Realm是什么

 

Realm是由Y Combinator公司孵化出來的一款可以用於iOS(同樣適用於Swift&Objective-C)和Android的跨平台移動數據庫。歷經幾年才打造出來,為了徹底解決性能問題,核心數據引擎用C++打造,並不是建立在SQLite之上的ORM,所以Realm相比SQLite和CoreData而言更快、更好、更容易去使用和完成數據庫的操作花費更少的代碼。它旨在取代CoredData和sqlite,它不是對coreData的簡單封裝、相反的,Realm它使用了它自己的一套持久化存儲引擎。而且Realm是完全免費的,這不僅讓它變得更加的流行也使開發者使用起來沒有任何限制。

 

Realm的特點

 

Realm以難以令人置信的快速和易用讓開發者能夠用僅僅幾行代碼完成你所需要的一切功能。它旨在打造讓用戶得在到移動領域離線時的最好體驗,我整理了Realm具有的如下特點:

 

  • 易安裝:正如你在將要看到的使用Realm工作。安裝Realm就像你想象中一樣簡單。在Cocoapods中使用簡單命令,你就可以使用Realm工作。

  • 速度上:Realm是令人無法想象的快速使用數據庫工作的庫。Realm比SQLite和CoreData更快,這里的數據就是最好的證明。

  • 跨平台:Realm數據庫文件能夠跨平台和可以同時在iOS和Andriod使用。無論你是使用Java, Objective-C, or Swift,你都可以使用你的高級模型。

  • 可擴展性:在開發你的移動App特別是如果你的應用程序涉及到大量的用戶和大量的記錄時,具有良好的可擴展性是非常重要的。

  • 好的文檔&支持:Realm團隊提供了可讀的,非常有組織的並且豐富的文檔。如果你遇到什么問題通過Twitter、GitHub或者Stackoverflow與他們交流。

  • 可靠性:Realm已經被巨頭公司使用在他們的移動App中,像Pinterest, Dubsmash, and Hipmunk。

  • 免費性:使用Realm的所有功能都是免費的。

  • 懶加載:只有當你真正訪問對象的值時候才真正從磁盤中加載進來。

 

必備條件

 

  • iOS7及以后, OS X 10.9及以后。

  • 至少Xcode7.3及以后。

  • Realm針對iOS分為兩部分,一個是Swift版本,一個是Objective-C版本。

 

Realm安裝

 

  • 動態Framework庫。直接下載最新Realm的release版本,拖入項目工程中即可。

  • CocoaPods管理工具。使用CocoaPods0.39版本以及上。

  • Carthage管理工具。使用Carthage 0.17.0版本及以上。

  • 靜態Framework庫。直接下載最新Realm的release版本,拖入項目工程中即可。

 

(注意:本文將重點講解使用CocoaPods的方式安裝Realm到項目中)

 

配置Xcode和所需的工具

 

在配置Xcode項目之前,請確保你的電腦上已經安裝了CocoaPods,因為我們會使用CocoaPods在項目中安裝Realm。如果你不熟悉CocoaPods,你可以查看給你的網上的相關教程。

 

現在創建一個新的工程,選擇”Single View Application”,命名為RealmDemo或者任何你想的名稱。請確保創建的項目是Objective-C語言。現在在終端打開你的項目目錄並執行如下的CocoaPods命令:

 

Pod init

 

然后,使用Xcode打開Podfile文件,向其中添加如下內容:

接下來執行Pod install命令去下載Realm安裝到項目中。完成之后,你將看到新的Xcode項目的workspace在Podfile文件被生成。請打開Xcode的RealmDemo.xworkspace和不要打開xcodeproj。打開workspace之后,你將看到Pod相關文件已經被集成進去了。

 

現在Xcode已經准備好可以使用Realm工作了,但是我們還將需要安裝如下工具為的是讓我們使用Realm更輕松,容易。

 

在Xcode中安裝Realm插件(僅僅用於Xcode8以下版本)

 

Realm團隊已經提供了非常有用的Xcode插件,它將被使用在生成Realm的模型。為了安裝插件,我們使用Alcatraz.對於那些不了解什么是Alcatraz的人來說,可以自行去搜索下,Alcatraz真的非常好用。為了安裝Alcatraz需要通過以下終端中的命令行並且重啟你的Xcode。

 

curl -fsSL https://raw.githubusercontent.com/supermarin/Alcatraz/master/Scripts/install.sh | sh

 

之后在Xcode中,選擇Window,就可以看到下拉列表中的Package Manager選項,就表明已經安裝成功。

 

在Xcode彈出窗口中選擇需要的插件或者模板,在搜索框中你可以搜索任何插件或者各種各樣的模板去自定義你的Xcode。在搜索框中輸入”Realm”和”RealmPlugin”插件將顯示出來,點擊進行安裝。

 

Realm瀏覽器

 

除了以上的工具和插件之外,還有一個就是Realm瀏覽器。這個瀏覽器可以幫助你去閱讀和編輯你的.realm數據庫文件。這些文件是在你的應用程序中被創建出來的,它包含了關於數據庫表中實體、屬性和記錄的信息。目前直接在Mac App Store中搜索Realm Browser進行下載即可。下載完成后,打開Realm瀏覽器選擇Tools-> Generate demo database。它將為你生成測試數據庫文件和你可以打開它看到里面的數據。當你打開你的demo database時你應該看到

 

正如你看到的Class RealmTestClass1,它有1000個記錄和它展示了不同的列數據。接下來我們將討論Realm模型支持的屬性類型。

 

Realm的數據庫創建

 

目前Realm提供了3種方式創建數據庫對象,一種是存儲在默認路徑下的數據庫,一種是我們可以指定自己指定數據庫文件的存儲路徑和只讀屬性,還有一種可以使用內存數據庫。

 

  • 默認Realm數據庫

 

RLMRealm *realm = [RLMRealm defaultRealm];

 

  • 自定義數據庫(名稱、路徑)

 

//注意:新版本中關於配置Relam的文件路徑包括名稱等等都是在RLMRealmConfiguration對象中完成。

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

// Use the default directory, but replace the filename with the username

config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]

                      URLByAppendingPathComponent:username]

                      URLByAppendingPathExtension:@"realm"];

// Set this as the configuration used for the default Realm

  [RLMRealmConfiguration setDefaultConfiguration:config];

 

或者是

 

NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSString *dbPath = [docPath stringByAppendingPathComponent:@"guojie.realm"];

RLMRealm *realm1 = [RLMRealm realmWithURL:url];

 

當然了,RLMRealmConfiguration對象中還可以設置是否只讀數據庫

 

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

// Get the URL to the bundled file

config.fileURL = [[NSBundle mainBundle] URLForResource:@"MyBundledData" withExtension:@"realm"];

// Open the file in read-only mode as application bundles are not writeable

config.readOnly = YES;

 

  • 內存數據庫

    正常的Realm數據庫是存儲在硬盤上的,但是我們可以通過在RLMRealmConfiguration中設置inMemoryIdentifier屬性來創建一個內存數據庫。

 

RLMRealmConfiguration *cfg = [RLMRealmConfiguration defaultConfiguration];

cfg.inMemoryIdentifier = @"test";

RLMRealm *realm = [RLMRealm realmWithConfiguration:cfg error:nil];

 

注意:內存數據庫在每次程序退出時不會保存數據。如果某個內存Realm實例沒有被引用,所有的數據在實例對象釋放的適合也會被釋放。建議你在app中用強引用來鉗制所有新建的內存Realm數據庫實例。

 

Realm的構建數據模型

 

Realm的數據模型是用傳統的 Objective-C 接口(interface)和屬性(@property)定義的。 只要定義 RLMObject的一個子類或者一個現成的模型類,你就能輕松創建一個Realm的數據模型對象。Realm模型對象和其他的Objective-C的功能很相似–你可以給它們添加你自己的方法和protocol然后和其他的對象一樣使用。 唯一的限制就是從它們被創建開始,只能在一個進程中被使用。如果你已經安裝了Xcode Plugin(Xcode版本

 

現在一起來使用Xcode中的Realmh插件去創建Realm類。打開Xcode和創建新文件。在右邊框中選擇Realm:

 

然后選擇名稱為Dog類名的Objective-C文件。現在你應該看到如下信息:

 

用一個Dog對象來表示一條小狗,緊接向類中添加屬性。然后類在添加屬性之后向如下這樣:

 

  • Dog.h

 

@interface Dog : RLMObject

/** 名字 */

@property (nonatomic, copy) NSString *name;

/** 年齡 */

@property (nonatomic, assign) NSInteger age;

/** 品種 */

@property (nonatomic, copy) NSString *type;

RLM_ARRAY_TYPE(Dog)

@end

 

  • Dog.m

 

@implementation Dog

 

/**

設置主鍵

*/

+ (NSString *)primaryKey{

    return @"numId";

}

/**

添加索引的屬性

*/

+ (NSArray *)indexedProperties{

    return @[@"name"];

}

/**

添加默認值

*/

+ (NSDictionary *)defaultPropertyValues{

    return @{@"type":@"taidi"

             };

}

@end

 

說明:

 

  1. Realm支持的屬性類型如下:BOOL, bool, int, NSInteger, long, float, double, CGFloat, NSString, NSDate 和 NSData。

  2. 你可以使用RLMArray和RLMObject來模擬對一或對多的關系(Realm也支持RLMObject繼承)

  3. Realm忽略了Objective-C的property attributes(如nonatomic, atomic, strong, copy, weak 等等)。 所以,推薦在創建模型的時候不要使用任何的property attributes。但是,假如你設置了,這些attributes會一直生效直到RLMObject被寫入realm數據庫。

  4. 定義了RLM_ARRAY_TYPE(Dog) 這個宏表示支持RLMArray該屬性

  5. 另外Realm提供了以下幾個方法供對屬性進行自定義:

    • + (NSArray *)indexedProperties: 可以被重寫來來提供特定屬性(property)的屬性值(attrbutes)例如某個屬性值要添加索引。

    • + (nullable NSDictionary *)defaultPropertyValue: 為新建的對象屬性提供默認值。

    • + (nullable NSString *)primaryKey: 可以被重寫來設置模型的主鍵。定義主鍵可以提高效率並且確保唯一性。

    • + (nullable NSArray *)ignoredProperties:可以被重寫來防止Realm存儲模型屬性。

    • - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block:自動更新對象數據。

    • + (NSArray *)requiredProperties:可選屬性

Realm的數據增刪改查

 

存儲數據

 

(1).創建Dog對象,依次設置各個屬性

 

Dog *myDog = [[Dog alloc]init];

myDog.numId = @"1";

myDog.name  = @"haha";

myDog.age   = 10;

myDog.type  = @"taidi";

 

(2).使用字典創建Dog對象

 

Dog *myDog2 = [[Dog alloc]initWithValue:@{@"numId":@"2",

                                       @"name" :@"xiaoxiao",

                                       @"age"  :@11,

                                       @"type" :@"heibei"

                                    }];

 

(3).使用數組創建Dog對象

 

Dog *myDog3 = [[Dog alloc]initWithValue:@[@"3",@"小小",@12,@"smy"]];

 

(4).除此之外,還支持嵌套的對象

 

如果在對象的屬性中有RLMObjects對象或者RLMArrays對象,在這種情況下當你使用字典或者數組途徑創建對象時,你就可以使用數組或者字典對象替換屬性來表示它的屬性。例如下面的嵌套對象例子:

 

// Instead of using already existing dogs...

Person *person1 = [[Person alloc] initWithValue:@[@"Jane", @30, @[aDog, anotherDog]]];

 

// ...we can create them inline

Person *person2 = [[Person alloc] initWithValue:@[@"Jane", @30, @[@[@"Buster", @5],

                                                                  @[@"Buddy", @6]]]];

 

說明:

 

1. 最明顯的是使用指定的初始化程序創建一個對象。注意被設置為必須的屬性在被添加之前一定要被設置值。

2. 對象可以通過傳入與屬性名稱一樣的字典Key和Values來創建。

3. 最后,對象可以使用數組創建,需要注意的是在數組中值的順序必須跟Model中相應屬性順序保持一致。

4. 需要注意的是: RLMArray僅僅只能包含RLMObject對象,而不包括基本數據類型例如NSString。

 

(5).存儲數據到Realm中

 

RLMRealm *realm = [RLMRealm defaultRealm];

[realm beginWriteTransaction];

[realm addObject:myDog];

[realm commitWriteTransaction];

 

請注意:當你同時執行多個寫入操作時,它們將會阻礙執行的所在線程。所以你應該考慮在單獨的線程中處理而不是UI線程。另一個就是,當你在執行寫入操作時,讀取操作不會被阻塞。這一點是很有用的,因為你的App可以執行許多讀取操作時在后台同一時間寫入事務可能也會在后台執行中。

 

刪除數據

 

  • 刪除指定的數據:

 

- (void)deleteObject:(RLMObject *)object;

 

  • 刪除一組數據:

 

- (void)deleteObjects:(id)array;

 

  • 刪除全部數據:

 

- (void)deleteAllObjects;

 

修改數據

 

如果該條數據不存在則會新家一條數據。

 

  • 針對單條數據進行的修改或新增:

 

- (void)addOrUpdateObject:(RLMObject *)object;

 

  • 針對一組數據的修改或新增:

 

- (void)addOrUpdateObjectsFromArray:(id)array;

 

  • 針對帶有主鍵的數據修改或新增

 

如果你的模型類中包含一個主鍵,Realm可以智能的以這個主鍵來更新或添加數據。

 

- (void)addOrUpdateObject:(RLMObject *)object;

 

  • 使用KVC修改數據

 

如果你是一個iOS開發者,你將很熟悉KVC,Realm類中例如對象、結果集、list也是兼容KVC的。這樣可以幫助你利用runtime設置/更新屬性。看看這個例子:

 

RLMResults *dogs = [Dog allObjects];

[[RLMRealm defaultRealm] transactionWithBlock:^{

  [[Dog firstObject] setValue:@20 forKeyPath:@"age"];

  // set each person's name property to "wangwu"

  [dogs setValue:@"wangwu" forKeyPath:@"name"];

}];

 

說明:對於增加、刪除、修改必須要在事務中進行操作。

 

查詢數據

 

查詢會返回RLMResults結果集,它里面包含了RLMObject對象的集合。RLMResults的接口方法非常類似於NSArray一樣可以使用下標索引訪問元素。與NSArray不同的是,RLMResults的元素類型只有單一的RLMObject的子類型。所有的查詢都是懶查詢,即只有當訪問屬性時,數據才會被讀取出來。查詢的結果不是拷貝本地磁盤中的數據而是直接原始數據,所以修改某一處的數據將會直接修改磁盤中的數據。在結果集被使用的過程中,查詢的執行是會被延遲的。這意味着鏈接幾個臨時中間的RLMResults所進行的排序和過濾數據的操作不執行額外的工作去處理中間的狀態。一旦查詢已經被執行或者通知block已經被添加,RLMResult在可能的情況下在后台線程上執行查詢以保證RLMResult的結果始終是Relam中最新的。

 

  • 查詢全部數據

 

RLMResults *dogs = [Dog allObjects];

 

或者指定Realm數據庫:

 

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSString *realmPath = [path stringByAppendingPathComponent:@"guojie.realm"];

RLMRealm *realm = [RLMRealm realmWithPath:realmPath];

RLMResults *results = [Dog allObjectsInRealm:realm];

 

在條件查詢中,如果你熟悉謂詞,那么你已經知道該如何查詢了。RLMObjects, RLMRealm, RLMArray, and RLMResults都提供了允許讓你通過傳遞一個謂詞實例、謂詞字符串、謂詞格式字符串來查詢具體RLMObject實例的方法。

 

  • 條件查詢

 

假設要查詢名字是guojie和年齡是10的dog對象:

 

RLMResults *results = [Dog objectsWhere:@"name = 'haha' AND age = 10"];

 

也可以使用謂詞查詢:

 

NSPredicate *pred = [NSPredicate predicateWithFormat:@"type = '%@' AND name = '%@'", @"taidi", @"haha"];

RLMResults *results = [Dog objectsWithPredicate:pred]

 

  • 條件排序

 

假設已經查詢出type是taidi,name是haha的數據結果集后,需要將結果按照num字段進行遞減排序:

 

NSPredicate *pred = [NSPredicate predicateWithFormat:@"type = '%@' AND name = '%@'", @"taidi", @"haha"];

RLMResults *results = [Dog objectsWithPredicate:pred];

results = [results sortedResultsUsingProperty:@"numId" ascending:NO];

 

請注意:結果集的順序只能保證在查詢排序時保持一致,出於性能的原因,查詢順序不保證被保存。如果你需要為何查詢的順序,則需要其他一些解決方案。

 

  • 鏈式查詢(結果過濾)

 

假設要查詢的還是type是taidi,name是haha的結果集,還可以這樣處理:

 

RLMResults *results1 = [Dog objectsWhere:@"type = 'taidi'"];

RLMResults *results2 = [results1 objectsWhere:@"name = 'haha'"];

 

通知

 

每當一次寫事務完成Realm實例都會向其他線程上的實例發出通知,可以通過注冊一個block來響應通知:

 

self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {

    [_listTableView reloadData];

}];

 

只要有任何的引用指向這個返回的notification token,它就會保持激活狀態。在這個注冊更新的類里,你需要有一個強引用來鉗制這個token, 因為一旦notification token被釋放,通知也會自動解除注冊。

 

@property (nonatomic, strong) RLMNotificationToken *token;

 

另外可以使用下面的方式解除通知:

 

[realm removeNotification:self.token];

 

Realm版本遷移

 

當你和數據庫打交道的時候,你需要改變數據模型(Model),但是因為Realm中的數據模型被定義為標准的Objective-C interfaces,要改變模型,就像改變其他Objective-C interface一樣輕而易舉。舉個例子,假設有個數據模型Person:

 

@interface Person : RLMObject

@property NSString *firstName;

@property NSString *lastName;

@property int age;

@end

 

當我們想添加一個字段fullName屬性而不是first和last names時,我們可以這樣做:

 

@interface Person : RLMObject

@property NSString *fullName;

@property int age;

@end

 

接下來執行遷移:

 

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

 

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

config.schemaVersion = 1;

config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {

  //如果從沒遷移過,oldSchemaVersion == 0

  if (oldSchemaVersion < 1) {

    // The enumerateObjects:block: method iterates

    // over every 'Person' object stored in the Realm file

    [migration enumerateObjects:Person.className

                          block:^(RLMObject *oldObject, RLMObject *newObject) {

 

      // 設置新增屬性的值

      newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",

                                         oldObject[@"firstName"],

                                         oldObject[@"lastName"]];

    }];

  }

};

[RLMRealmConfiguration setDefaultConfiguration:config];

 

在遷移的過程中重命名屬性名稱

 

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

 

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

config.schemaVersion = 1;

config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {

  // We haven’t migrated anything yet, so oldSchemaVersion == 0

  if (oldSchemaVersion < 1) {

    // The renaming operation should be done outside of calls to `enumerateObjects:`.

    [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];

  }

};

[RLMRealmConfiguration setDefaultConfiguration:config];

 

JSON

 

Realm並沒有直接的支持JSON,但是它可以使用[NSJSONSerialization JSONObjectWithData:options:error:]轉化之后的結果。在結合標准的API中的createOrUpdateInRealm方法就可以解決。

 

// A Realm Object that represents a city

@interface City : RLMObject

@property NSString *name;

@property NSInteger cityId;

// other properties left out ...

@end

@implementation City

@end // None needed

 

NSData *data = [@"{\"name\": \"San Francisco\", \"cityId\": 123}" dataUsingEncoding: NSUTF8StringEncoding];

RLMRealm *realm = [RLMRealm defaultRealm];

 

// 插入包含JSON的NSData數據

[realm transactionWithBlock:^{

  id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];

  [City createOrUpdateInRealm:realm withValue:json];

}];

 

說明:如果在JSON中包含有嵌套的對象或數組,那么會被自動被映射到一對多關系相關的嵌套對象中。

 

多線程訪問Realm

 

當需要從多線程去訪問同一個Realm時,你必須初始化一個新的Realm,去得到在App中每一個線程有一個不同的實例。只要你指定相同的Realm配置,那么所有的Realm實例將指向磁盤上的同一個文件。Realm是不支持在線程之間共享Relam實例的。當在一個單一事務中通過批量處理來寫入非常大的數據,使用多線程是非常有用的。為了避免阻塞主線程,寫入事務可以放在使用GCD在后台進行。Realm對象不是線程安全的,所有決定了不能線程間共享。因此你必須在你想要讀取或者寫入的每一個線程中獲取到Realm的對象實例。如下是在后台隊列中插入100萬個對象的例子:

 

dispatch_async(queue, ^{

  @autoreleasepool {

    // Get realm and table instances for this thread

    RLMRealm *realm = [RLMRealm defaultRealm];

 

    // Break up the writing blocks into smaller portions

    // by starting a new transaction

    for (NSInteger idx1 = 0; idx1 < 1000; idx1++) {

      [realm beginWriteTransaction];

 

      // Add row via dictionary. Property order is ignored.

      for (NSInteger idx2 = 0; idx2 < 1000; idx2++) {

        [Person createInRealm:realm

                    withValue:@{@"name"      : randomString,

                                @"birthdate" : randomDate}];

      }

 

      // Commit the write transaction

      // to make this data available to other threads

      [realm commitWriteTransaction];

    }

  }

});

 

總結

 

Realm是非常適合去管理本地存儲和數據庫。Realm給了你代碼層面非常強大的可擴展性和力量。對於大多數App甚至是游戲,我認為你是需要使用數據庫,Realm值得你去使用。

 

參考資料

 

  • Building a ToDo App Using Realm and Swift

    https://www.appcoda.com/realm-database-swift/

  • iOS中Realm數據庫的基本用法

    http://blog.devzeng.com/blog/simple-usage-of-realm-in-ios.html

  • Realm-在iOS中應用-全新的數據庫思維模式

    http://blog.csdn.net/xiaoluodecai/article/details/50618445

  • Realm數據庫基礎教程

    http://www.cocoachina.com/ios/20150505/11756.html

  • 移動端數據庫新王者:realm

    http://ios.jobbole.com/85041/

  • Realm Tutorial: Getting Started

    https://www.raywenderlich.com/112544/realm-tutorial-getting-started

     

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com