你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 手把手教你從Core Data遷移到Realm

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

編輯:IOS開發基礎

1463023394508339.jpg

投稿文章,作者:一縷殇流化隱半邊冰霜(簡書)

前言

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

  • 使用方便

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

Realm比其他的對象關系映射型數據庫(Object Relational Mapping),甚至比原生的SQLite更加快,這都得益於它零拷貝的設計。看看iOS用戶和Android用戶都是怎麼評價它的快的Twitter

  • 跨平台

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

  • 優秀的特性

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

  • 值得信任

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

  • 社區驅動

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

  • 支持

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

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

1194012-c5a9a2eba990151e.jpg

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

1194012-f44984d9ac96595c.jpg

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

1194012-c808e85259fbccf5.jpg

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

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

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

原文:Migrating an App from Core Data to 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 
//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的聲明額外的說明。

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

在寫數據的過程中(這個過程不會太長!),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種情況:

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

  • 如果用戶數據不是不可替代的——舉個例子,如果是一些簡單的緩存信息,可以通過硬盤上的用戶數據重新生成的話,那麼可以很簡單的就把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,看看它是否適用於你。如果適用,請你告訴我們!

感謝閱讀這篇文章。快去用Realm構建一個令人驚喜的app吧!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved