「最简单」的 Core Data 上手指南
- 原文地址:The Easiest Core Data
- 原文作者:Alberto De Bortoli
- 譯文出自:掘金翻譯計(jì)劃
- 譯者:Zheaoli
- 校對(duì)者:Kulbear,?cbangchen
在過(guò)去的幾個(gè)月里,我花費(fèi)了大量的時(shí)間在研究 Core Data 之上,我得去處理一個(gè)使用了很多陳舊的代碼,糟糕的 Core Data 以及違反了多線程安全的項(xiàng)目。講真,Core Data 學(xué)習(xí)起來(lái)非常的困難,在學(xué)習(xí) Core Data 的時(shí)候,你肯定會(huì)感到迷惑和一種深深的挫敗感。正是因?yàn)檫@些原因,我決定給出一種超級(jí)簡(jiǎn)單的解決方案。這個(gè)方案的特點(diǎn)就是簡(jiǎn)潔,線程安全,非常易于使用,這個(gè)方案能滿(mǎn)足你大部分對(duì)于 Core Data 的需求。在經(jīng)過(guò)若干次的迭代后,我所設(shè)計(jì)的方案最終成為一個(gè)成熟的方案。
OK,女士們,先生們,現(xiàn)在請(qǐng)?jiān)试S我隆重向您介紹?Skiathos?和?Skopelos。其中?Skiathos?是基于Objective-C?所開(kāi)發(fā)的,而?Skopelos?則基于?Swift?所開(kāi)發(fā)的。這兩個(gè)框架的名字來(lái)源于希臘的兩個(gè)島,在這里,我渡過(guò)了2016年的夏天,同時(shí),在這里完成了兩個(gè)框架的編寫(xiě)工作。
寫(xiě)在前面的話
整個(gè)項(xiàng)目的目的就是能夠讓您以及其簡(jiǎn)便的方式在您的 App 中引入 Core Data。
我們將從如下幾個(gè)方面來(lái)進(jìn)行一個(gè)介紹:
- CoreDataStack
- AppStateReactor
- DALService (Data Access Layer)
CoreDataStack
如果你有過(guò)使用 Core Data 的經(jīng)驗(yàn),那么你應(yīng)該知道創(chuàng)建一個(gè)堆棧是一個(gè)充滿(mǎn)陷阱的過(guò)程。這個(gè)組件是用于創(chuàng)建堆棧(用于管理?Obejct Context?),具體的設(shè)計(jì)說(shuō)明可以參看 Marcus Zarra 所寫(xiě)的這篇文章。
其中一個(gè)和 Magical Record 或者其余第三方插件不同的是,整個(gè)存儲(chǔ)過(guò)程都是在一個(gè)方向上發(fā)起的,可能是從某個(gè)子節(jié)點(diǎn)向下或者向上傳遞來(lái)進(jìn)行持久化儲(chǔ)存。其余的組件允許你創(chuàng)建以?private context?作為父節(jié)點(diǎn)的子節(jié)點(diǎn),這將會(huì)導(dǎo)致?main context?不能被更新,同時(shí)只能通過(guò)通知的方式來(lái)進(jìn)行合并更新。main context?是相對(duì)固定的并與?UI?進(jìn)行了綁定:這樣較為簡(jiǎn)單的方式可以幫助開(kāi)發(fā)者更好的去完成一個(gè) APP 的開(kāi)發(fā)。
AppStateReactor
唔,其實(shí)你可以忽略這一段。這個(gè)組件屬于 CoreDataStack ,在 App 切換至后臺(tái),失去節(jié)點(diǎn),或者即將退出時(shí),它負(fù)責(zé)監(jiān)視相對(duì)應(yīng)的修改,并把其保存。
DALService (Data Access Layer) / (Skiathos/Skopelos)
如果你擁有使用 Core Data 的經(jīng)驗(yàn),那么你也應(yīng)該知道,我們大部分操作都是重復(fù)的,我們經(jīng)常在一個(gè) context 中調(diào)用?performBlock:/performBlockAndWait:?函數(shù),而這個(gè) Context 提供了一個(gè)最終會(huì)調(diào)用save:?作為最終語(yǔ)句的 block 。數(shù)據(jù)庫(kù)的所有操作都是基于 API 中所提供的?read:?和?write:?:這兩個(gè)協(xié)議提供了 CQRS (命令和查詢(xún)分離) 的實(shí)現(xiàn)。用于讀取的代碼塊將在主體中進(jìn)行運(yùn)行(因?yàn)檫@被認(rèn)為是一個(gè)已確定的單個(gè)資源)。用于寫(xiě)入的代碼塊將會(huì)在一個(gè)子線程中運(yùn)行,這樣可以保證實(shí)時(shí)的進(jìn)行數(shù)據(jù)儲(chǔ)存,變化的數(shù)據(jù)將會(huì)在不會(huì)阻塞主線程的情況下通過(guò)異步的方式進(jìn)行儲(chǔ)存。write:completion:方法將會(huì)程序運(yùn)行完后來(lái)對(duì)數(shù)據(jù)的更改進(jìn)行持久化儲(chǔ)存。
換句話說(shuō),寫(xiě)入的數(shù)據(jù)在?main managed object context?和最后持久化過(guò)程中都會(huì)保證其一致性。在 主要管理對(duì)象的?context?中,相應(yīng)的數(shù)據(jù)也能保證其可用性。
Skiathos/Skopelos?是?DALService?的子類(lèi), 這樣可以給這個(gè)組件一個(gè)比較好聽(tīng)的名字。
使用介紹
在使用這一系列組件之前,你首先需要?jiǎng)?chuàng)建一個(gè)類(lèi)型為?Skiathos?的屬性,然后以下面這種方式去初始化它:
| 1 2 3 | self.skiathos = [Skiathos setupInMemoryStackWithDataModelFileName:@"<#datamodelfilename>"]; // or self.skiathos = [Skiathos setupSqliteStackWithDataModelFileName:@"<#datamodelfilename>"]; |
在使用?Skopelos?時(shí),代碼如下所示:
| 1 2 3 | self.skopelos = SkopelosClient(inMemoryStack: "<#datamodelfilename>") // or self.skopelos = SkopelosClient(sqliteStack: "<#datamodelfilename>") |
你可以通過(guò)使用依賴(lài)注入的方式來(lái)在應(yīng)用的其余地方使用這些對(duì)象。不得不說(shuō),為 Core Data 棧上的不同對(duì)象創(chuàng)建單例是一種很不錯(cuò)的做法。當(dāng)然,不斷的創(chuàng)建實(shí)例的開(kāi)銷(xiāo)是十分巨大的。通常來(lái)講,我們不是很推薦使用單例模式。單例模式的測(cè)試性不強(qiáng),在使用過(guò)程中,使用者無(wú)法有效的控制其聲明周期,這樣可能會(huì)違背一些最佳實(shí)踐的編程原則。正是因?yàn)槿绱?#xff0c;在這個(gè)庫(kù)里,我們不推薦使用單例。
由于下面幾個(gè)原因,你在使用時(shí)需要從?Skiathos/Skopelos?進(jìn)行繼承:
- 創(chuàng)建一個(gè)全局可共享的實(shí)例。
- 重載?handleError(error: NSError)?方法,以便在你的程序里出現(xiàn)一些錯(cuò)誤時(shí),這個(gè)方法能夠正常的被調(diào)用。
為了創(chuàng)建單例,你應(yīng)該如下面的示例一樣去從?Skiathos/Skopelos?進(jìn)行繼承:
單例
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @interface SkiathosClient : Skiathos + (SkiathosClient *)sharedInstance; @end static SkiathosClient *sharedInstance = nil; @implementation SkiathosClient + (SkiathosClient *)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [self setupSqliteStackWithDataModelFileName:@"<#datamodelfilename>"]; </#datamodelfilename> }); return sharedInstance; } - (void)handleError:(NSError *)error { // clients should do the right thing here NSLog(@"%@", error.description); } @end |
或者是
| 1 2 3 4 5 6 7 | class SkopelosClient: Skopelos { static let sharedInstance = Skopelos(sqliteStack: "DataModel") override func handleError(error: NSError) { // clients should do the right thing here print(error.description) } } |
讀寫(xiě)操作
寫(xiě)到這里,讓我們同時(shí)看看在一個(gè)標(biāo)準(zhǔn) Core Data 的操作方式和我們組件所提供的方式吧。
標(biāo)準(zhǔn)的讀取姿勢(shì):
| 1 2 3 4 5 6 7 8 9 10 11 | __block NSArray *results = nil; NSManagedObjectContext *context = ...; [context performBlockAndWait:^{ NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:NSStringFromClass(User) inManagedObjectContext:context]; [request setEntity:entityDescription]; NSError *error; results = [context executeFetchRequest:request error:&error]; }]; return results; |
標(biāo)準(zhǔn)的寫(xiě)入姿勢(shì):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | NSManagedObjectContext *context = ...; [context performBlockAndWait:^{ User *user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(User) inManagedObjectContext:context]; user.firstname = @"John"; user.lastname = @"Doe"; NSError *error; [context save:&error]; if (!error) { // continue to save back to the store } }]; |
Skiathos?中的讀取姿勢(shì):
| 1 2 3 4 | [[SkiathosClient sharedInstance] read:^(NSManagedObjectContext *context) { NSArray *allUsers = [User allInContext:context]; NSLog(@"All users: %@", allUsers); }]; |
Skiathos?中的寫(xiě)入姿勢(shì):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Sync [[SkiathosClient sharedInstance] writeSync:^(NSManagedObjectContext *context) { User *user = [User createInContext:context]; user.firstname = @"John"; user.lastname = @"Doe"; }]; [[SkiathosClient sharedInstance] writeSync:^(NSManagedObjectContext *context) { User *user = [User createInContext:context]; user.firstname = @"John"; user.lastname = @"Doe"; } completion:^(NSError *error) { // changes are saved to the persistent store }]; // Async [[SkiathosClient sharedInstance] writeAsync:^(NSManagedObjectContext *context) { User *user = [User createInContext:context]; user.firstname = @"John"; user.lastname = @"Doe"; }]; [[SkiathosClient sharedInstance] writeAsync:^(NSManagedObjectContext *context) { User *user = [User createInContext:context]; user.firstname = @"John"; user.lastname = @"Doe"; } completion:^(NSError *error) { // changes are saved to the persistent store }]; |
Skiathos?當(dāng)然也支持鏈?zhǔn)秸{(diào)用:
| 1 2 3 4 5 6 7 8 9 10 11 | __block User *user = nil; [SkiathosClient sharedInstance].write(^(NSManagedObjectContext *context) { user = [User createInContext:context]; user.firstname = @"John"; user.lastname = @"Doe"; }).write(^(NSManagedObjectContext *context) { User *userInContext = [user inContext:context]; [userInContext deleteInContext:context]; }).read(^(NSManagedObjectContext *context) { NSArray *users = [User allInContext:context]; }); |
如果是在 Swift中,代碼將會(huì)變成下面這個(gè)樣子
讀取:
| 1 2 3 4 | SkopelosClient.sharedInstance.read { context in let users = User.SK_all(context) print(users) } |
寫(xiě)入:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Sync SkopelosClient.sharedInstance.writeSync { context in let user = User.SK_create(context) user.firstname = "John" user.lastname = "Doe" } SkopelosClient.sharedInstance.writeSync({ context in let user = User.SK_create(context) user.firstname = "John" user.lastname = "Doe" }, completion: { (error: NSError?) in // changes are saved to the persistent store }) // Async SkopelosClient.sharedInstance.writeAsync { context in let user = User.SK_create(context) user.firstname = "John" user.lastname = "Doe" } SkopelosClient.sharedInstance.writeAsync({ context in let user = User.SK_create(context) user.firstname = "John" user.lastname = "Doe" }, completion: { (error: NSError?) in // changes are saved to the persistent store }) |
鏈?zhǔn)秸{(diào)用:
| 1 2 3 4 5 6 7 8 9 10 11 12 | SkopelosClient.sharedInstance.write { context in user = User.SK_create(context) user.firstname = "John" user.lastname = "Doe" }.write { context in if let userInContext = user.SK_inContext(context) { userInContext.SK_remove(context) } }.read { context in let users = User.SK_all(context) print(users) } |
NSManagedObject?類(lèi)所提供了非常清楚的?CRUD?方法。在作為讀/寫(xiě)代碼塊的參數(shù)傳遞之時(shí),對(duì)象應(yīng)該被作為一個(gè)整體進(jìn)行處理。你應(yīng)該優(yōu)先使用這些內(nèi)建的方法。主要的方法有下面這些:
| 1 2 3 4 5 6 7 | + (instancetype)SK_createInContext:(NSManagedObjectContext *)context; + (NSUInteger)SK_numberOfEntitiesInContext:(NSManagedObjectContext *)context; - (void)SK_deleteInContext:(NSManagedObjectContext *)context; + (void)SK_deleteAllInContext:(NSManagedObjectContext *)context; + (NSArray *)SK_allInContext:(NSManagedObjectContext *)context; + (NSArray *)SK_allWithPredicate:(NSPredicate *)pred inContext:(NSManagedObjectContext *)context; + (instancetype)SK_firstInContext:(NSManagedObjectContext *)context; |
| 1 2 3 4 5 6 7 | static func SK_create(context: NSManagedObjectContext) -> Self static func SK_numberOfEntities(context: NSManagedObjectContext) -> Int func SK_remove(context: NSManagedObjectContext) -> Void static func SK_removeAll(context: NSManagedObjectContext) -> Void static func SK_all(context: NSManagedObjectContext) -> [Self] static func SK_all(predicate: NSPredicate, context:NSManagedObjectContext) -> [Self] static func SK_first(context: NSManagedObjectContext) -> Self? |
注意,在使用?SK_inContext(context: NSManagerObjectContext)?時(shí),不同的讀寫(xiě)代碼塊可能會(huì)得到同一個(gè)對(duì)象。
線程安全
所有 DALService 所產(chǎn)生的實(shí)例都可以認(rèn)為是線程安全的。
我們特別建議你在項(xiàng)目中進(jìn)行這樣的設(shè)置?-com.apple.CoreData.ConcurrencyDebug 1?,這可以確保你不會(huì)在多線程和并發(fā)的情況下濫用 Core Data。
這個(gè)組件不是為了通過(guò)隱藏?ManagedObjectContext:?的概念來(lái)達(dá)到接口引入的目的:它將會(huì)在客戶(hù)端中引入更多的線程問(wèn)題,因?yàn)殚_(kāi)發(fā)者有責(zé)任去檢查所調(diào)用線程的類(lèi)型(而那將會(huì)是在忽視 Core Data 所帶給我們的好處)。
原文發(fā)布時(shí)間為:2016年09月10日
本文來(lái)自云棲社區(qū)合作伙伴掘金,了解相關(guān)信息可以關(guān)注掘金網(wǎng)站。
總結(jié)
以上是生活随笔為你收集整理的「最简单」的 Core Data 上手指南的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: BZOJ-1057: [ZJOI2007
- 下一篇: Qt网络编程之实例一GET方式