優(yōu)秀的 CoreData 便捷存取框架尖坤,MagicalRecord

MagicalRecord是受Ruby on Rails 中 Active Record fetching便捷性的啟發(fā)而成的庫 稳懒。MagicalRecord的目標(biāo)是:

精簡 CoreData 相關(guān)代碼

可以清晰、簡單慢味、單行獲取數(shù)據(jù)

當(dāng)需要對請求進行優(yōu)化的時候场梆,仍可以修改NSFetchRequest

文檔

安裝

添加MagicalRecord很簡單,你可以從下列方式中任選其一:

使用 Carthage 安裝

1.在項目文件夾內(nèi)創(chuàng)建文件Cartfile纯路,并添加以下內(nèi)容:github "MagicalPanda/MagicalRecord"

2.打開終端進入項目目錄或油,運行命令

carthage update

3.將Carthage/Build/目錄下的 MagicalRecord.framework 拖拽到需要項目中。

使用 CocoaPods 安裝

使用CocoaPods將 MagicalRecord 也非常簡便:

向 Podfile 中添加一下內(nèi)容:a.純凈安裝

pod "MagicalRecord"

b.使用 CocoaLumberjack 紀(jì)錄日志

pod "MagicalRecord/CocoaLumberjack"

2.進入項目目錄驰唬,運行

pod update

3.向項目源文件添加 #import就可以開始使用了顶岸。

使用 Xcode 子項目

Xcode 子項目可以將MagicalRecord 作為內(nèi)在依賴使用。

1.將Magicalrecord作為 Git 子模型(submodule)添加到項目中叫编,

$ cd MyXcodeProjectFolder

$ git submodule add https://github.com/magicalpanda/MagicalRecord.git Vendor/MagicalRecord

$ git commit -m "Add MagicalRecord submodule"

2.將 Vendor/MagicalRecord/ 目錄下的 MagicalRecord.xcproj 拖拽到你的項目中 3.點擊左側(cè)項目欄 ,target 欄下選擇需要添加 MagicalRecord 的項目 4.選擇 Build Phases 項并展開 Link Binary With Libraries 欄 5.點擊 + 號添加對應(yīng)平臺的MagicalRecord framework 6.現(xiàn)在在你的目標(biāo)源文件中辖佣,可以通過 #import添加使用 MagicalRecord。

小貼士

由于iOS平臺 UIKit 不包含 Core Data搓逾,如果你在Xcode設(shè)置Link Frameworks Automatically 為 NO卷谈,你需要手動添加 CoreData.framework。如果是 OS X 平臺恃逻,Core Data 已包含在 Cocoa 中雏搂,無需再手動添加。

分類方法的簡略表達方式

MagicalRecord 提供的分類方法寇损,均以 MR_作前綴凸郑。這遵循為防止命名沖突,蘋果建議為分類方法添加前綴矛市。

如果你想使用無前綴的分類方法芙沥,可以導(dǎo)入以下頭文件:

如果你使用的是Swift語言,你需要將頭文件導(dǎo)入到target的 Objective-C bridging header(橋接頭文件)中浊吏。

導(dǎo)入頭文件之后而昨,在使用MagicalRecord之前,還需要先調(diào)用類方法+[MagicalRecord enableShorthandMethods]:

- (void)theMethodWhereYouSetupMagicalRecord

{

[MagicalRecord enableShorthandMethods];

// 正常設(shè)置 MagicalRecord

}

請注意找田,我們不提供此專題的技術(shù)支持歌憨。如果無法使用,請?zhí)峤诲e誤墩衙,我們將盡快修復(fù)务嫡。

開始使用

在工程的 PCH預(yù)編譯頭文件中導(dǎo)入 MagicalRecord.h 頭文件,既可全局使用漆改。

如果你使用的是 CocoaPods 或者 MagicalRecord.framework心铃,導(dǎo)入代碼如下:

每次調(diào)用都會實例化一塊 Core Data 棧,并且對該實例提供 getter 和 setter 方法挫剑。 MagicalRecord 將這些實例作為默認(rèn)使用的棧去扣。

當(dāng)在DEBUG模式下使用默認(rèn)SQLite 數(shù)據(jù)存儲時,如果沒有創(chuàng)建新數(shù)據(jù)模型就更改了數(shù)據(jù)模型樊破,MagicalRecord 將會自動刪除舊的存儲并創(chuàng)建新的存儲愉棱。這將為你節(jié)省大量時間——每當(dāng)更改數(shù)據(jù)模型,不必再卸載、重裝應(yīng)用捶码。但請確保發(fā)布的應(yīng)用的不是DEBUG版本:否則在未告知用戶的情況下刪除應(yīng)用數(shù)據(jù)羽氮,這可不是好事!

在應(yīng)用退出之前惫恼,需要調(diào)用 +cleanUp 方法:

[MagicalRecord cleanUp];

此方法將進行清理工作档押,清理自定義的錯誤處理,同時將MagicalRecord 創(chuàng)建的 CoreData 棧設(shè)為nil祈纯。

啟用 iCloud 持久化存儲

要使用蘋果 iCloud Core Data 同步存儲數(shù)據(jù)令宿,使用下列方法替代上一小節(jié)中的方法:

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

localStoreNamed:(NSString *)localStore;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreNamed:(NSString *)localStoreName

cloudStorePathComponent:(NSString *)pathSubcomponent;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreNamed:(NSString *)localStoreName

cloudStorePathComponent:(NSString *)pathSubcomponent

completion:(void (^)(void))completion;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

localStoreAtURL:(NSURL *)storeURL;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreAtURL:(NSURL *)storeURL

cloudStorePathComponent:(NSString *)pathSubcomponent;

+ (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID

contentNameKey:(NSString *)contentNameKey

localStoreAtURL:(NSURL *)storeURL

cloudStorePathComponent:(NSString *)pathSubcomponent

completion:(void (^)(void))completion;

想了解更多內(nèi)容,請查看Apple’s “iCloud Programming Guide for Core Data”腕窥。

提示

如果你需要同時管理多個iCloud 存儲內(nèi)容粒没,我們推薦使用可以設(shè)置 contentNameKey 的方法。不能設(shè)置contentNameKey的方法則會根據(jù)應(yīng)用的 bundle identifier(CFBundleIdentifier)自動生成 NSPersistentStoreUbiquitousContentNameKey作為標(biāo)示簇爆。

使用托管對象上下文(managed object Context)

創(chuàng)建新的托管對象上下文

MagicalRecord提供了很多便捷的類方法癞松,幫助你創(chuàng)建托管對象上下文(managed object context):

+ [NSManagedObjectContext MR_newContext]: 將默認(rèn)上下文設(shè)為父上下文爽撒,其并發(fā)類型為 NSPrivateQueueConcurrencyType

+ [NSManagedObjectContext MR_newMainQueueContext]: 并發(fā)類型為NSMainQueueConcurrencyType

+ [NSManagedObjectContext MR_newPrivateQueueContext]: 并發(fā)類型為NSPrivateQueueConcurrencyType.

+ [NSManagedObjectContext MR_newContextWithParent:…]: 可以設(shè)置父上下文,并發(fā)類型 NSPrivateQueueConcurrencyType.

+ [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 可以為新上下文設(shè)置持久化存儲協(xié)調(diào)器(persistent store coordinator)响蓉,并發(fā)類型 NSPrivateQueueConcurrencyType.

默認(rèn)上下文(Default Context)

使用 Core Data 時硕勿,經(jīng)常需要處理 NSManagedObject 和 NSManagedObjectContext 對象。

MagicalRecord 提供了便捷的類方法獲取默認(rèn)托管對象上下文NSManagedObjectContext枫甲。獲取的上下文在主線程中執(zhí)行操作源武,并可貫穿整個app進行操作,非常適合單線程的簡單應(yīng)用想幻。

獲取方法:NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

通過 MagicalRecord 粱栖,這個上下文可以在任何有需要的方法內(nèi)使用。但是這些方法不能使用上下文參數(shù)來指定特定上下文脏毯。

如果你需要一個不在主線程執(zhí)行的上下文闹究,使用此方法:

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];

此方法得到的上下文與默認(rèn)上下文具有相同的數(shù)據(jù)結(jié)構(gòu)(object model)和持久化存儲(persistent store),并安全的在其他線程中執(zhí)行食店。同時跋核,它的父上下文(parent context)為默認(rèn)上下文。

如果你想在后臺上下文 myNewContext中執(zhí)行所有獲取請求(fetch requests)叛买,使用:[NSManagedObjectContext MR_setDefaultContext:myNewContext];

注意:我們強烈建議在主線程中創(chuàng)建并設(shè)置默認(rèn)上下文砂代,同時將默認(rèn)上下文的類型設(shè)置為NSMainQueueConcurrencyType。

在后臺線程中執(zhí)行任務(wù)

MagicalRecord 提供了創(chuàng)建及使用后臺線程上下文的方法率挣。后臺保存操作為代碼塊的形式刻伊,和 UIView 動畫方法類似,但也有細微區(qū)別:

更改實體的塊不會在主線程中執(zhí)行

代碼塊只提供一個 NSManagedObjectContext 對象

舉個例子椒功,假如我們有一個名為Person 的實體捶箱,需要同時設(shè)置它的屬性 firstName 和 lastName, 我們可以使用 MagicalRecord 創(chuàng)建后臺上下文并進行保存操作:

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

Person *localPerson = [person MR_inContext:localContext];

localPerson.firstName = @"John";

localPerson.lastName = @"Appleseed";

}];

這個方法中,block 將為你提供合適的上下文來執(zhí)行操作动漾,所以你不必再考慮如何設(shè)置上下文了丁屎。當(dāng)提供的上下文完成操作后,將通知默認(rèn)上下文已更改了實體旱眯,并進行更新操作晨川。

如果你想在“保存塊”(save block)執(zhí)行完成之后進行其他動作,可以使用“完成塊” completion block:

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

Person *localPerson = [person MR_inContext:localContext];

localPerson.firstName = @"John";

localPerson.lastName = @"Appleseed";

} completion:^(BOOL success, NSError *error) {

self.everyoneInTheDepartment = [Person findAll];

}];

colpletion block 在主線程(隊列)中執(zhí)行删豺,所以UI 相關(guān)的操作可以放在這個塊中共虑。

創(chuàng)建實體對象

創(chuàng)建并插入新的實體實例到默認(rèn)上下文(default context)中:

Person *myPerson = [Person MR_createEntity];

創(chuàng)建并將實體插入到指定上下文中:

Person *myPerson = [Person MR_createEntityInContext:otherContext];

刪除實體對象

刪除默認(rèn)上下文中實體:

[myPerson MR_deleteEntity];

刪除指定上下文中實體:

[myPerson MR_deleteEntityInContext:otherContext];

刪除默認(rèn)上下文中所有實體:

[Person MR_truncateAll];

刪除指定上下文中所有實體:

[Person MR_truncateAllInContext:otherContext];

獲取實體對象

基礎(chǔ)查詢

MagicalRecord 中大多數(shù)查詢方法返回 NSArray 類型的結(jié)果。

假設(shè)我們有一個名為 Person 的實體呀页,且與實體 Department 相關(guān)聯(lián)妈拌。從持久化存儲(persistent store)中查詢所有 Person 實體:

NSArray *people = [Person MR_findAll]; //

查詢所有Person實體,按照指定屬性排序:

//按照屬性 LastName 升序(asceding)排序

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName" ascending:YES];

查詢所有Person實體蓬蝶,按照多個屬性排序:

//按照屬性 LastName 升序尘分,F(xiàn)irstName 升序排序

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName" ascending:YES];

查詢所有Person實體猜惋,按照多個屬性排序,指定的多個屬性可以設(shè)置不同的排序方式培愁,如果某個屬性未指明排序方式惨奕,則按方法中”ascending:”參數(shù)進行排序:

//按照屬性LastName降序、FirstName升序排序

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName"

ascending:YES];

// 等效于

NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES"

ascending:NO];

查詢特定屬性值的實體竭钝,使用此方法:

//所有 FirstName 為 Forrest 的實體

Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];

高級查詢

使用 謂詞 查詢特定實體:

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];

NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];

獲取 NSFetchRequest 對象

使用謂詞獲取對應(yīng)查詢條件的 NSFetchRequest 對象

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];

自定義查詢

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSFetchRequest *peopleRequest = [Person MR_requestAllWithPredicate:peopleFilter];

[peopleRequest setReturnsDistinctResults:NO];

[peopleRequest setReturnPropertiesNamed:@[@"FirstName", @"LastName"]];

NSArray *people = [Person MR_executeFetchRequest:peopleRequest];

查詢實體數(shù)目

你還可以獲取指定類型的實體個數(shù):

//返回類型為NSNumber

NSNumber *count = [Person MR_numberOfEntities];

使用謂詞或其它過濾器查詢實體數(shù)目:

//返回類型為NSNumber

NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];

以下方法返回類型則是NSUInteger:

+ (NSUInteger) MR_countOfEntities;

+ (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context;

+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter;

+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter

inContext:(NSManagedObjectContext *)context;

合計操作(Aggregate Operations)

NSNumber *totalCalories = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:"

onAttribute:@"calories"

withPredicate:predicate];

NSNumber *mostCalories? = [CTFoodDiaryEntry MR_aggregateOperation:@"max:"

onAttribute:@"calories"

withPredicate:predicate];

NSArray *caloriesByMonth = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:"

onAttribute:@"calories"

withPredicate:predicate

groupBy:@"month"];

在指定上下文查找

所有的 find, fetch, request 方法都可以通過 inContext: 參數(shù)指定查詢的上下文:

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];

Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName"

withValue:@"Gump"

inContext:someOtherContext];

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

保存實體

保存操作的時機

大多數(shù)情況下,當(dāng)數(shù)據(jù)發(fā)生變化的時候就執(zhí)行保存操作雹洗。有一些應(yīng)用只是在應(yīng)用關(guān)閉的時候才保存香罐,但這樣增加了丟失數(shù)據(jù)的風(fēng)險。當(dāng)應(yīng)用崩潰的時候就會造成數(shù)據(jù)丟失时肿,用戶會丟失他們的數(shù)據(jù)庇茫。應(yīng)當(dāng)避免這種情況的發(fā)生。

如果保存操作花費的時間太長螃成,你可以采取以下措施:

1.在后臺線程中執(zhí)行保存操作:

MagicalRecord 為更改旦签、保存實體提供了簡潔的API。

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

// Do your work to be saved here, against the `localContext` instance

// 在此塊中的所有操作均在后臺執(zhí)行

} completion:^(BOOL success, NSError *error) {

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

2.將任務(wù)拆分成小任務(wù)保存:

像一次性導(dǎo)入大量數(shù)據(jù)的任務(wù)寸宏,你需要將數(shù)據(jù)拆分成小塊來操作宁炫。一次處理的數(shù)據(jù)大小并沒有統(tǒng)一標(biāo)準(zhǔn),你需要使用類似 Apples’ Instruments 的工具來測試調(diào)整氮凝,獲取最佳大小羔巢。

處理長時儲存

iOS平臺

當(dāng)iOS平臺app退出時,app會獲得短暫時間向硬盤中保存整理數(shù)據(jù)罩阵。如果你的應(yīng)用存儲時間較長竿秆,最好在應(yīng)用完全終止前,請求延長后臺執(zhí)行時間稿壁。

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

// 此處執(zhí)行保存操作

} completion:^(BOOL success, NSError *error) {

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

請確保認(rèn)真閱讀beginBackgroundTaskWithExpirationHandler 幽钢,因為不適當(dāng)或不必要的延長應(yīng)用生命周期,申請上架的時候可能會被 App Store 拒絕傅是。

OS X 平臺

OS X Mavericks (10.9) 及更高版本中匪燕,App Nap 可以讓你 app 看起來像關(guān)閉了,但仍在后臺運行喧笔。如果有長時間的保存操作谎懦,最好的方式是禁用自動終止和突然終止( automatic and sudden termination):

NSProcessInfo *processInfo = [NSProcessInfo processInfo];

[processInfo disableSuddenTermination];

[processInfo disableAutomaticTermination:@"Application is currently saving to persistent store"];

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

// Do your work to be saved here

} completion:^(BOOL success, NSError *error) {

[processInfo enableSuddenTermination];

[processInfo enableAutomaticTermination:@"Application has finished saving to the persistent store"];

}];

與iOS方法相同,使用前請仔細閱讀 《NSProcessInfo文檔》 溃斋。

MagicalRecord 2.3.0 版本變化

不建議使用 Context For Current Thread

早期發(fā)布的MagicalRecord中界拦,我們提供了獲取當(dāng)前線程中數(shù)據(jù)上下文(managed object context )的方法。不幸的是梗劫,這個方法已經(jīng)不能正確獲取當(dāng)前線程中的上下文了享甸。 Grand Central Dispatch (GCD) 導(dǎo)致一個隊列(queue)可能被分發(fā)到多個線程中截碴,而CoreData 使用 GCD 之前,舊方法中使用的是舊的 NSThread API 蛉威。如果想了解更多內(nèi)容日丹,請看 Saul 的文章Why contextForCurrentThread Doesn’t Work in MagicalRecord 。

為了兼容舊版本蚯嫌,2.3.0 版本中仍然有 +MR_contextForCurrentThread 方法哲虾。但我們不推薦使用。

特別是不要再+[MagicalRecord saveWithBlock:…] 方法內(nèi)使用 +MR_contextForCurrentThread 方法——返回的上下文很可能是錯的择示!

如果你現(xiàn)在仍然想使用舊方法束凑,請使用可以接受上下文參數(shù)的變體,并將 +[MagicalRecord saveWithBlock:…]中塊的上下文參數(shù)(context)作為它的參數(shù)栅盲。

廢棄方法:

[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {

NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntity];

// …

}];

替代方法:

[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {

NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntityInContext:localContext];

// …

}];

當(dāng)MagicalRecord 更新到 3.0 版汪诉,獲取當(dāng)前線程上下文的方法會被完全移除。未設(shè)置“上下文(context)”的方法谈秫,將使用默認(rèn)上下文——詳情見 MagicalRecord 3.0 版文檔扒寄。

MagicalRecord 2.2.0 版本變化

在 MagicalRecord 2.2.0版中, 保存相關(guān)的 API 修改的更為統(tǒng)一拟烫,同時也更符合Core Data 中的命名方式该编。同時加入了大量自動測試來確保保存方法,無論新舊硕淑,在更新后仍可以使用上渴。

MR_save 方法暫時恢復(fù)為在當(dāng)前線程同步運行,同時保存到持久化存儲中喜颁。但在下一個主要版本 (MagicalRecord 3.0)中稠氮,將會移除MR_save 方法。如果你和在未來版本的庫保持一致半开,現(xiàn)在應(yīng)使用 MR_saveToPersistentStoreAndWait 方法隔披。

新方法:

下列為新添加的方法:

NSManagedObjectContext+MagicalSaves

- (void) MR_saveOnlySelfWithCompletion:(MRSaveCompletionHandler)completion;

- (void) MR_saveToPersistentStoreWithCompletion:(MRSaveCompletionHandler)completion;

- (void) MR_saveOnlySelfAndWait;

- (void) MR_saveToPersistentStoreAndWait;

- (void) MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion;

MagicalRecord+Actions

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;

+ (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveUsingCurrentThreadContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;

+ (void) saveUsingCurrentThreadContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block;

棄用方法

新方法將替代以下方法,下列方法將在 MagicalRecord 3.0 中移除:

NSManagedObjectContext+MagicalSaves

- (void) MR_save;

- (void) MR_saveWithErrorCallback:(void(^)(NSError *error))errorCallback;

- (void) MR_saveInBackgroundCompletion:(void (^)(void))completion;

- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback;

- (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;

- (void) MR_saveNestedContexts;

- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback;

- (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;

MagicalRecord+Actions

+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block;

+ (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))completion;

+ (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(void))completion errorHandler:(void (^)(NSError *error))errorHandler;

導(dǎo)入數(shù)據(jù)(importing data)

我們正在著手更新文檔寂拆,更新期間請參考 Cocoa Is My Girlfriend 中的文章 Importing Data Made Easy 奢米。本節(jié)大部分內(nèi)容是參考 Saul 的文章撰寫的。

MagicalRecord 可以直接從標(biāo)準(zhǔn) NSObject 對象(NSArray纠永、NSDictionary等)導(dǎo)入到Core Data 存儲鬓长。

使用MagicalRecord 將數(shù)據(jù)從外部資源導(dǎo)入到持久化存儲中,需要兩步工作:

1.定義導(dǎo)入的數(shù)據(jù)與持久化存儲之間的映射關(guān)系

2.執(zhí)行導(dǎo)入數(shù)據(jù)操作

定義導(dǎo)入映射

外部數(shù)據(jù)源在質(zhì)量(quality)和結(jié)構(gòu)(structure)上千差萬別尝江,我們盡可能讓 MagicalRecord 能夠應(yīng)對各種情形涉波。

MagicalRecord 可以從任何遵守鍵值編碼(KVC)的對象導(dǎo)入數(shù)據(jù)。 大家通常只使用 NSArray 和 NSDictionary 對象作為數(shù)據(jù)源,實際上MagicalRecord 適用于任何兼容KVC的 NSObject 子類啤覆。

MagicalRecord 利用 Xcode數(shù)據(jù)建模工具“User Info” 的值苍日,無需更改代碼即可配置導(dǎo)入選項(import option)和映射(mapping)。

供參考:user info 的鍵值以字典(NSDictionary)的形式附加到數(shù)據(jù)模型的實體(entity)窗声、屬性(attribute)和關(guān)系(relationship)中相恃,可以通過 NSEntityDescription 對象的 userInfo 方法對其訪問。

在 Xcode 數(shù)據(jù)建模工具的幫助下笨觅,可以直接通過數(shù)據(jù)模型(Data Model)的 Inspector 窗口中的“User Info”更改這個字典拦耐。編輯數(shù)據(jù)模型時,可以通過 View > Utilities > Show Data Model Inspector 或按 ?+?+3 打開 Inspector 窗口见剩。

默認(rèn)情況下杀糯,MagicalRecord 會根據(jù)導(dǎo)入數(shù)據(jù)的鍵(key)的名稱,自動匹配實體(entity)的屬性(attribute)和關(guān)系(relationships)炮温。如果數(shù)據(jù)模型的鍵(key)與屬性、關(guān)系名稱相匹配牵舵,那么你不需要做任何操作——MagicalRecord便會自動導(dǎo)入該鍵對應(yīng)的值(value)柒啤。

舉個例子,實體有名為 ‘firstName’ 的屬性(attribute)畸颅,MagicalRecord將假定導(dǎo)入的數(shù)據(jù)中有同名的鍵(key)——如果同名鍵真的存在担巩,那么將使用 firstName的值,來為實體firstName 屬性賦值没炒。

但多數(shù)情況下并沒有那么理想涛癌,要導(dǎo)入的數(shù)據(jù)的鍵(key)和結(jié)構(gòu)(structure)并不能匹配實體的屬性和關(guān)系。此時就需要通過設(shè)置數(shù)據(jù)的鍵與實體的屬性之間的映射關(guān)系來導(dǎo)入了送火。

Core Data 中我們需要處理的三個對象——實體, 屬性和關(guān)系 ——需要通過user info key 來設(shè)置其參數(shù):

Attributes

Key Type Purpose

attributeValueClassName String 待定(TBD)

dateFormat String 待定. 默認(rèn)格式 yyyy-MM-dd'T'HH:mm:ssz.

mappedKeyName String 指明導(dǎo)入數(shù)據(jù)的keypath拳话,支持多層級,以 .作為分隔符, 例如location.latitude

mappedKeyName.[0-9] String 備用keypath种吸,當(dāng)mappedKeyName指定的keypath不存在時使用弃衍。語法同上。

useDefaultValueWhenNotPresent Boolean 為true時坚俗,如果導(dǎo)入數(shù)據(jù)值為空镜盯,則為此屬性設(shè)置默認(rèn)值。

Entities

Key Type Purpose

relatedByAttribute String 指明連接兩個實體的關(guān)系(relationship)的目標(biāo)實體的屬性(attribute)

Relationships

Key Type Purpose

mappedKeyName String 指明導(dǎo)入數(shù)據(jù)的keypath猖败,支持多層級速缆,以 .作為分隔符, 例如location.latitude

mappedKeyName.[0-9] String 備用keypath,當(dāng)mappedKeyName指定的keypath不存在時使用恩闻。語法同上艺糜。

relatedByAttribute String 指明關(guān)系(relationship)的目標(biāo)的屬性(attribute)

type String TBD

導(dǎo)入對象

導(dǎo)入之前你應(yīng)當(dāng)充分了解導(dǎo)入數(shù)據(jù)對象的結(jié)構(gòu),同時構(gòu)建與之對應(yīng)的實體。完成這些之后倦踢,再進行數(shù)據(jù)導(dǎo)入送滞。導(dǎo)入的方式有以下幾種:

依據(jù)數(shù)據(jù)對象自動創(chuàng)建新實例:

NSDictionary *contactInfo = // 此處解析 JSON 或其他數(shù)據(jù)源

Person *importedPerson = [Person MR_importFromObject:contactInfo];

兩步方法:

NSDictionary *contactInfo = // 此處解析 JSON 或其他數(shù)據(jù)源

Person *person = [Person MR_createEntity]; // 此處不一定是新建實體,也可使用已有實體

[person MR_importValuesForKeysWithObject:contactInfo];

兩步方法中的實體可以是新建的辱挥,也可以是已存在的犁嗅,兩步方法可以幫你更新已存在的實體。

+MR_importFromObject:根據(jù)已配置的查詢值(lookup value)查找相應(yīng)對象(即上文中的relatedByAttribute 和 attributeNameID)晤碘。它遵循Cocoa導(dǎo)入key-value 的范例褂微,保證安全導(dǎo)入數(shù)據(jù)。

+MR_importFromObject: 是對實例方法-MR_importValuesForKeysWithObject:和創(chuàng)建對象方法的封裝园爷,返回值為賦值的新對象宠蚂。

以上兩個方法均為同步執(zhí)行方法。導(dǎo)入時間長短不一童社,很可能影響交互體驗求厕,若將所有數(shù)據(jù)導(dǎo)入工作都放在后臺執(zhí)行,就不會影響軟件交互了扰楼。像前面提到的呀癣,MagicalRecord 提供了很多好用的后臺線程 API :

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {

Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext]; //導(dǎo)入操作

}];

導(dǎo)入數(shù)組

使用 array 對象來保存單一類型數(shù)據(jù)很常見,可以使用 +MR_importFromArray: 方法來處理:

NSArray *arrayOfPeopleData = /// 解析JSON的結(jié)果

NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

此方法與+MR_importFromObject:方法類似弦赖,也是同步執(zhí)行方法项栏,所以你想要后臺執(zhí)行,就得使用前面提到的方法蹬竖,在后臺塊里執(zhí)行沼沈。

如果你導(dǎo)入的數(shù)據(jù)與Core Data model 完全匹配,那么上面的方法已經(jīng)能幫你完成導(dǎo)入工作了币厕。但大多數(shù)情況下并不理想列另,兩者之間稍有差別, MagicalRecord 的一些方法處理導(dǎo)入數(shù)據(jù)和Core Data model之間的不同。

實踐

錯誤數(shù)據(jù)處理

API 方法經(jīng)常會返回格式錯誤或內(nèi)容錯誤的數(shù)據(jù)旦装。最好的處理方法是通過實體類的分類方法處理访递,有三個方法可供選擇:

Method Purpose

- (BOOL) shouldImport; 在導(dǎo)入數(shù)據(jù)前調(diào)用此方法。如果返回為 NO同辣,則取消向?qū)嶓w導(dǎo)入數(shù)據(jù)拷姿。

- (void) willImport:(id)data; 緊鄰數(shù)據(jù)導(dǎo)入之前調(diào)用。

- (void) didImport:(id)data; 緊鄰數(shù)據(jù)導(dǎo)入之后調(diào)用旱函。

一般來說响巢,如果你嘗試導(dǎo)入所有數(shù)據(jù)時,發(fā)現(xiàn)一些損壞數(shù)據(jù)棒妨,你可能想修復(fù)它們踪古。

常見的情景是在導(dǎo)入JSON數(shù)據(jù)時含长,常常將數(shù)字字符串被錯誤的解析為數(shù)字。如果你想確保它們以字符串的類型導(dǎo)入伏穆,可以這么做:

@interface MyGreatEntity

@property(readwrite, nonatomic, copy) NSString *identifier;

@end

@implementation MyGreatEntity

@dynamic identifier;

- (void)didImport:(id)data

{

if (NO == [data isKindOfClass:[NSDictionary class]]) {

return;

}

NSDictionary *dataDictionary = (NSDictionary *)data;

id identifierValue = dataDictionary[@"my_identifier"];

if ([identifierValue isKindOfClass:[NSNumber class]]) {

NSNumber *numberValue = (NSNumber *)identifierValue;

self.identifier = [numberValue stringValue];

}

}

@end

更新數(shù)據(jù)時刪除本地記錄

在后續(xù)的導(dǎo)入操作中拘泞,有時不僅需要更新記錄,同時要刪除本地存在枕扫、遠程數(shù)據(jù)庫中不存在的數(shù)據(jù)陪腌。此時需要先根據(jù) relatedByAttribute來判斷哪些記錄不在遠程數(shù)據(jù)庫中,然后將其刪除烟瞧。

NSArray *arrayOfPeopleData = /// JSON 解析結(jié)果

NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

NSArray *idList = [arrayOfPeopleData valueForKey:@"id"];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@)", idList];

[Person MR_deleteAllMatchingPredicate:predicate];

如果你想在更新操作中確保相關(guān)記錄已被刪除诗鸭,你可以使用與上面代碼類似的邏輯,并在Person 的 willImport:方法中實現(xiàn):

@implementation Person

-(void)willImport:(id)data {

NSArray *idList = [data[@"posts"] valueForKey:@"id"];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@) AND person.id == %@", idList, self.id];

[Post MR_deleteAllMatchingPredicate:predicate];

}

記錄日志

MagicalRecord 已經(jīng)在Core Data 大部分交互中內(nèi)置了日志紀(jì)錄参滴。讀取强岸、保存數(shù)據(jù)時,如果啟用了日志砾赔,錯誤信息將會被打印到控制臺(console)蝌箍。

在調(diào)試(debug)模式下,日志會打印調(diào)試信息(MagicalRecordLoggingLevelDebug)暴心,在發(fā)布(release)模式下妓盲,日志則會輸出錯誤信息(MagicalRecordLoggingLevelError)。

可以通過調(diào)用[MagicalRecord setLoggingLevel:]方法來配置日志酷勺;選擇使用以下幾個日志等級:

MagicalRecordLogLevelOff: 關(guān)閉日志

MagicalRecordLoggingLevelError: 記錄所有錯誤

MagicalRecordLoggingLevelWarn: 記錄警告和錯誤信息

MagicalRecordLoggingLevelInfo: 記錄有用信息本橙,警告和錯誤信息

MagicalRecordLoggingLevelDebug: 記錄所有調(diào)試扳躬、有用信息脆诉、警告和錯誤信息

MagicalRecordLoggingLevelVerbose: 記錄診斷信息,有用信息贷币、錯誤和警告

日志默認(rèn)配置為MagicalRecordLoggingLevelWarn击胜。

CocoaLumberjack

如果你的項目支持 CocoaLumberjack,MagicalRecord 將把日志交由CocoaLumberjack處理役纹。你所需要做的就是在導(dǎo)入 MagicalRecord 之前導(dǎo)入 CocoaLumberjack,例如:

其他資料

以下為安裝使用 MagicalRecord 的相關(guān)文章:

How to make Programming with Core Data Pleasant

Using Core Data with MagicalRecord

Super Happy Easy Fetching in Core Data

Core Data and Threads, without the Headache

Unit Testing with Core Data

技術(shù)支持

MagicalRecord is provided as-is, free of charge促脉。如果需要技術(shù)支持辰斋,你可以選擇以下途徑:

在 Stackoverflow.com 提出你的問題并添加 MagicalRecord 標(biāo)簽旁仿。只有添加了 MagicalRecord 標(biāo)簽病蛉,核心團隊才能看到你的問題。Stack Overflow 社區(qū)可以幫你迅速得到解答,卤橄,MagicalRecord is provided as-is, free of charge。 如果社區(qū)無法回答你的問題处窥,那我們將嘗試介入并回答你的提問沽瞭。

如果你發(fā)現(xiàn)MagicalRecord中存在 bug, 請在 Github Issues page for MagicalRecord 頁面提交 a support ticket 布疙。我們將以最快速度解決蚊惯。另外,請不要在 issue tracker 界面提一般問題灵临。Support questions will be closed unanswered

對于個人問題或者需要緊急技術(shù)支持截型,可以 MagicalPanda 獲取咨詢服務(wù)

Twitter

關(guān)注 twitter @MagicalRecord 獲取 MagicalRecord 最新信息。

開源地址:https://github.com/magicalpanda/MagicalRecord

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末儒溉,一起剝皮案震驚了整個濱河市宦焦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌顿涣,老刑警劉巖波闹,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異园骆,居然都是意外死亡舔痪,警方通過查閱死者的電腦和手機寓调,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門锌唾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夺英,你說我怎么就攤上這事晌涕。” “怎么了痛悯?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵余黎,是天一觀的道長。 經(jīng)常有香客問我载萌,道長惧财,這世上最難降的妖魔是什么巡扇? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮垮衷,結(jié)果婚禮上厅翔,老公的妹妹穿的比我還像新娘。我一直安慰自己搀突,他們只是感情好刀闷,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仰迁,像睡著了一般甸昏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徐许,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天施蜜,我揣著相機與錄音,去河邊找鬼雌隅。 笑死役衡,一個胖子當(dāng)著我的面吹牛相艇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼额嘿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汉形?” 一聲冷哼從身側(cè)響起痢虹,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梯皿,沒想到半個月后仇箱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡东羹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年剂桥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片属提。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡权逗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冤议,到底是詐尸還是另有隱情斟薇,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布恕酸,位于F島的核電站堪滨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蕊温。R本人自食惡果不足惜袱箱,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一遏乔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧发笔,春花似錦按灶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至量蕊,卻和暖如春铺罢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背残炮。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工韭赘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人势就。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓泉瞻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親苞冯。 傳聞我的和親對象是個殘疾皇子袖牙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容