手把手教你從Core Data遷移到Realm

前言

看了這篇文章的標題,也許有些人還不知道Realm是什么,那么我先簡單介紹一下這個新生的數據庫。號稱是用來替代SQLite 和 Core Data的。Realm有以下優點:

  1. 使用方便
    Realm并不是基于SQLite的對象關系映射數據庫。它是使用自己的持久化引擎,為簡單和速度而生。用戶們說,他們在數分鐘之內就上手了Realm,構建一個app只需要數小時,每個app開發時間至少節約數周的時間。


  2. Realm比其他的對象關系映射型數據庫(Object Relational Mapping),甚至比原生的SQLite更加快,這都得益于它零拷貝的設計??纯?a target="_blank" rel="nofollow">iOS用戶和Android用戶都是怎么評價它的快的Twitter

  3. 跨平臺
    Realm 支持 iOS 和 OS X (Objective?C & Swift) 和Android。你可以通過使用相同的model,共享Realm文件到各個平臺,Java,Swift,Objective-C。并且在全平臺可以使用相同的業務邏輯

  4. 優秀的特性
    Realm支持先進的特性,如加密,圖形查詢輕松的遷移。Realm的API是一個非常適合打造高響應的應用程??序,并且Realm為我們提供方便的組件,以輕松構建復雜的用戶界面

  5. 值得信任
    Realm已經獲得了銀行,醫療保健提供商,復雜的企業app,星巴克這些產品的青睞。

  6. 社區驅動
    Realm是Github上星標最多的數據庫里面排名第四,僅次于Java 和 Cocoa 的repos。除了核心工程之外,Realm的社區已經編譯了上百個app插件和組件

  7. 支持
    可以從Realm公司快速獲得官方的答案,去編譯和支持你的數據庫。Realm的團隊會在Github, StackOverflow, & Twitter回答大家的各種問題

下面再發3張令人驚喜的性能對比圖

上圖是每秒能在20萬條數據中進行查詢后count的次數。realm每秒可以進行30.9次查詢后count。SQLite僅僅只有每秒13.6次查詢后的count,相對于Core Data只有可憐的1。

在20萬條中進行一次遍歷查詢,數據和前面的count相似:Realm一秒可以遍歷20萬條數據31次,而RCore Data只能進行兩次查詢。 SQLite也只有14次而已。

這是在一次事務每秒插入數據的對比,Realm每秒可以插入9.4萬條記錄,在這個比較里純SQLite的性能最好,每秒可以插入17.8萬條記錄。然而封裝了SQLite的FMDB的成績大概是Realm的一半,4.7萬,Core Data就更低了,只有可憐的1.8萬。

從以上3張圖可以看出Realm優秀的特性。那么我們開始使用Realm吧。第一步就是把本地的數據庫換成Realm。

下面是我翻譯的一篇手把手教程,那么讓我們趕緊通過教程,來把Core Data遷移到Realm吧。

原文

譯文

把一個使用core data框架作為數據庫存儲方式的app,遷移到Realm的確是一件很容易的事情。如果你現在有一個已經用了Core Data的app,并且考慮換成Realm,這個手把手教程正適合你!

很多開發者在用戶界面,高度集成了Core Data(有時可能有上千行代碼),這時很多人會告訴你轉換Core Data到Realm可能會花數小時。Core Data和Realm兩者都是把你的數據當成Object看待,所以遷移通常是很直接的過程:把你已經存在的Core Data的代碼重構成使用Realm API的過程是很簡單的。

遷移后,你會為Realm為你app帶來的易用性,速度快,和穩定性而感到興奮。

1.移除Core Data Framework

首先,如果你的app當前正在使用Core Data,你需要找出哪些代碼是包含了Core Data的代碼。這些代碼是需要重構的。幸運的是,這里有一個手動的方式去做這件事:你可以手動的在整個代碼里面搜索相關的代碼,然后刪除每個導入了Core Data頭文件聲明的語句

#import <CoreData/CoreData.h>
//or
@import CoreData;

一旦這樣刪除以后,每一行使用了Core Data的將會報一個編譯錯誤,接下來,解決這些編譯錯誤只是時間問題。

2.移除Core Data的設置代碼

在Core Data中,對model objects的更改是要通過managed object context object來實現的。而managed object context objects又是被persistent store coordinator object創建的,它們兩者又是被managed object model object創建的。

可以這么說,在你開始思考用Core Data讀取,或者寫入數據的時候,你通常需要在你的app中的某處去設置依賴的對象,暴露一些Core Data的方法給你的app邏輯使用。無論在你的application delegate中,全局的單例中,或者就是在inline實現中,這些地方都會存在大量的潛在的Core Data 設置代碼。

當你準備轉換到Realm時,所有的這些代碼都可以刪掉。

在Realm中,所有設置都在你第一次創建一個Realm object的時候就已經都完成了。當然也是可以手動去配置它,就像你指定Realm數據文件存儲在你的硬盤的哪個路徑下,這些完全都可以在runtime的時候去選擇的。

RLMRealm *defaultRealm = [RLMRealm defaultRealm];
//or
let realm = Realm()

感覺很好吧?

3.遷移model文件

在Core Data中,實用的那些類都是被定義成NSManagedObject的子類。這些object的接口都是很標準的,原始的類型(比如NSInteger 和 CGFloat)是不能被使用的,它們必須抽象成一個NSNumber對象。

@interface Dog : NSManagedObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSDate *birthdate;

@end

@implementation Dog

@dynamic name;
@dynamic age;
@dynamic birthdate;

@end

把這些managed object subclasses轉換成Realm是非常簡單的:

@interface Dog : RLMObject

@property NSString *uuid;
@property NSString *name;
@property NSInteger age;
@property NSDate *birthdate;

@end

@implementation Dog

+ (NSString *)primaryKey
{
    return @"uuid";
}

+ (NSDictionary *)defaultPropertyValues
{
    return @{ @"uuid" : [[NSUUID UUID] UUIDString],
              @"name" : @"",
              @"birthdate" : [NSDate date]};
}

@end

或者


class Dog: Object {
    dynamic var uuid = NSUUID().UUIDString
    dynamic var name = ""
    dynamic var age = 0
    dynamic var birthdate = NSDate().date
    
    override static func primaryKey() -> String? {
        return "uuid"
    }
}

完成!這是多么的簡單?

看這些實現,還是有一些Realm的細節需要注意的。

對于初次使用Realm的人來說,沒有必要去指定屬性關鍵字,Realm在內部已經管理了。所以這些類的頭文件看上去都很精簡。此外,Realm支持簡單的數據類型,比如NSInteger 和 CGFloat,所有所有的NSNumber都可以安全的刪除。

另一方面,這有一些關于Realm model的聲明額外的說明。

  1. Core Data objects通過內部的NSManagedObjectID屬性去唯一標識一個objects,Realm把這個留給開發者去完成。在上面的例子中,我們額外添加了一個名為uuid的屬性,然后通過調用 [RLMObject primaryKey]方法去作為這個class的唯一標識。當然,如果你的objects完全不需要唯一標識,這些都可以跳過。

  2. 在寫數據的過程中(這個過程不會太長!),Realm不能處理nil的object的屬性。原因是,在[RLMObject defaultPropertyValues]這個類方法中給每個object在最初創建的時候,每個object屬性都定義了一系列default值。當然這只是暫時的,我們很高興的告訴你,在接下來的更新中,我們將會支持Realm object的屬性可以為nil。

4.遷移寫操作

如果你不能保存你的數據,這肯定不是一個持久的方案!創建一個新的Core Data對象然后再簡單的修改一下它,需要下面這些代碼:


//Create a new Dog
Dog *newDog = [NSEntityDescription insertNewObjectForEntityForName:@"Dog" inManagedObjectContext:myContext];
newDog.name = @"McGruff";

//Save the new Dog object to disk
NSError *saveError = nil;
[newDog.managedObjectContext save:&saveError];

//Rename the Dog
newDog.name = @"Pluto";
[newDog.managedObjectContext save:&saveError];

相比之下,Realm保存的操作是略有不同的,但在相同的范圍內修改上面的代碼,仍然有相似的地方。

//Create the dog object
Dog *newDog = [[Dog alloc] init];
newDog.name = @"McGruff";

//Save the new Dog object to disk (Using a block for the transaction)
RLMRealm *defaultRealm = [RLMRealm defaultRealm];
[defaultRealm transactionWithBlock:^{
    [defaultRealm addObject:newDog];
}];

//Rename the dog (Using open/close methods for the transaction)
[defaultRealm beginWriteTransaction];
newDog.name = @"Pluto";
[defaultRealm commitWriteTransaction];

或者

//Create the dog object
let mydog = Dog()
myDog.name = "McGruff"

//Save the new Dog object to disk (Using a block for the transaction)
Realm().write {
    realm.add(myDog)
}

//Rename the dog (Using open/close methods for the transaction)
Realm().beginWrite()
myDog.name = "Pluto"
Realm().commitWrite()

完成!我們的數據被保存了!

明顯的不同是,在Realm中,一旦一個objects被添加到一個Realm object中,它就是不可被修改的。為了在修改屬性操作的后面執行,Realm object會被保存在一個寫的事務中。這種不能被修改的model,保證了在不同線程中讀/寫 object數據的情況下,數據的一致性。

Core Data的實現確實可以改變屬性,然后調用save方法,對比Realm的實現,只是一些小小的不同罷了。

5.遷移查詢

另一方面,如果你不能檢索查詢你的數據,這肯定不是一個持久的方案!

在Core Data的基礎實現中,它運用了fetch requests的概念去從硬盤檢索數據。一個fetch request object是被當成一個單獨的實例化對象去創建的,包含了一些額外的過濾參數,排序條件。


NSManagedObjectContext *context = self.managedObjectContext;

//A fetch request to get all dogs younger than 5 years old, in alphabetical order
NSEntityDescription *entity = [NSEntityDescription
                               entityForName:@"Dog" inManagedObjectContext:context];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age < 5"];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];

NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
request.predicate = predicate;
request.sortDescriptors = @[sortDescriptor];

NSError *error;
NSArray *dogs = [moc executeFetchRequest:request error:&error];

雖然這確實挺好,但是需要編寫大量的代碼!一些聰明的開發者就開發了一些library使這些代碼編寫的更加容易。比如MagicalRecord。

對比這些,使用了Realm之后,這些查詢的等效代碼如下:

RLMResults *dogs = [[Dog objectsWhere:@"age < 5"] sortedResultsUsingProperty:@"name" ascending:YES];

或者

var dogs = Realm().objects(Dog).filter("age < 5").sorted("name")

在一行調用了2個方法。對比Core Data將近10行代碼。

當然,相同操作得到的結果是相同的(RLMResults 和 NSArray 基本類似),轉換到Realm,由于這些查詢都是很獨立的,所以查詢周圍的邏輯只需要重構很少的一部分代碼就可以了。

6.遷移用戶數據

一旦你所有代碼都遷移到Realm,這里還有一個突出的問題,你如何遷移所有用戶已經存在在他們設備上的數據,從Core Data遷移到Realm中?

顯然,這是非常復雜的問題,它決定于你的app的功能,還有用戶的環境。你處理這種情況可能解決辦法每次都不一樣。

目前,我們看到了2種情況:

  1. 一旦你遷移到Realm,你可以重新導入Core Data framework到你的app,用原生的NSManagedObject objects去fetch你的用戶的Core Data數據,然后手動的把數據傳給Realm。你可以把這段遷移的代碼永久的留在app中,或者也可以經過非常充足的時間之后,再刪除掉。

  2. 如果用戶數據不是不可替代的——舉個例子,如果是一些簡單的緩存信息,可以通過硬盤上的用戶數據重新生成的話,那么可以很簡單的就把Core Data數據直接清除掉,當用戶下次打開app的時候,一切從0開始。當然這需要經過非常謹慎的考慮,不然的話,會給很多人留下非常壞的用戶體驗。

最終,決定應該偏向于用戶。理想的情況是不要留下Core Data還連接著你的app,但是結果還是要取決于你的情況。好運!

進一步的討論

雖然在移植一個應用程序到Realm過程中,沒有真正重要的步驟,但是有一些額外的情況下,你應該知道:

并發

如果你在后臺線程做了一些比較重的操作,你可能會發現你需要在線程之間傳遞Realm object。在Core Data中允許你在線程之間傳遞managed objects(雖然這樣做不是最佳實踐),但是在Realm中,在線程中傳遞objects是嚴格禁止的,并且任何企圖這樣做的,都會拋出一個嚴重的異常。

如此來說,對于下面這些情況,是件很容易的事情:


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    //Rename the dog in a background queue
    [[RLMRealm defaultRealm] transactionWithBlock:^{
        dog.name = @"Peanut Wigglebutt";
    }];
    
    //Print the dog's name on the main queue
    NSString *uuid = dog.uuid;
    dispatch_async(dispatch_get_main_queue(), ^{
        Dog *localDog = [Dog objectForPrimaryKey:uuid];
        NSLog(@"Dog's name is %@", localDog.name);
    });
});

或者


dispatch_async(queue) {
    //Rename the dog in a background queue
    Realm().write {
        dog.name = "Peanut Wigglebutt"
    }
    
    //Print the dog's name on the main queue
    let uuid = dog.uuid
    dispatch_async(dispatch_get_main_queue()) {
        let localDog = Realm().objectForPrimaryKey(Dog, uuid)
        println("Dog's name is \\(localDog.name)")
    }
}

雖然Realm objects不能在線程間被傳遞,但是Realm properties的副本可以在線程中被傳遞??紤]到Realm從磁盤中檢索objects是非??焖俚模绻皇呛唵蔚耐ㄟ^新線程在存儲區中重新refetch相同的object,這只會造成很小的性能損失。在這個例子中,我們取了對象的主鍵的copy,然后把它從后臺隊列傳遞給主隊列,然后再通過它在主線程的上下文中重新獲取該對象。

NSFetchedResultsController 的等效做法

相比Core Data的所有缺點,可能使用Core Data最充足的理由就是NSFetchedResultsController——這是一個類,它可以檢測到數據存儲的變化,并且能自動的把這一變化展示到UI上。

在寫這篇文章的時候,Realm還沒有相似的機制。雖然它可以注冊一個block,這個block會在數據源發生變化的時候被執行,但是這種"蠻力"的做法對大多數的UI來說都是不友好的。目前,如果你的UI代碼很依賴Realm,那么這種做法對你來說就像處理一個breaker一樣。

Realm的cocoa工程師現在正在開發一套通知系統,當一些object的屬性被更改的時候,允許我們去注冊一個通知,來接收到這些改變。這些特性都會在Realm的Swift and Objective?C 的未來的更新版本中。

在此期間,如果現有的通知block API還是沒有滿足你的需要,但是你還是需要當特定的property被更改了收到一個通知,這里推薦使用神奇的第三方庫,名字叫RBQFetchedResultsController,它能模仿上述功能。除此之外,你還可以通過在objects里面加入setter方法,當setter方法被調用的時候,發送一個廣播通知,這樣做也能實現相同的功能。

結尾

Core Data和Realm的在展示數據的時候都是通過model objects,由于這一相似性,得以讓我們從Core Data遷移到Realm時非常迅速,簡單(并且非常令人滿意!)。盡管開始看上去令人怯步,但是實際做起來,就是需要把每個Core Data的方法調用轉換成等價的Realm的方法,然后寫一個輔助類去幫你遷移用戶的數據。這些也都非常簡單。

如果你在你的app中使用Core Data遇到了些困難,需要些更加簡單的解決辦法,我們強烈推薦你嘗試一下Realm,看看它是否適用于你。如果適用,請你告訴我們!

感謝閱讀這篇文章??烊ビ肦ealm構建一個令人驚喜的app吧!在這些地方可以聯系到我們StackOverflow, GitHub, or Twitter.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容