投稿文章,作者:一縷殇流化隱半邊冰霜(簡書)
前言
看了這篇文章的標題,也許有些人還不知道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張令人驚喜的性能對比圖
上圖是每秒能在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吧。
原文: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吧!