優秀的 CoreData 便捷存取框架,MagicalRecord

MagicalRecord是受Ruby on Rails 中 Active Record fetching便捷性的啟發而成的庫 。MagicalRecord的目標是:

精簡 CoreData 相關代碼

可以清晰、簡單、單行獲取數據

當需要對請求進行優化的時候,仍可以修改NSFetchRequest

文檔

安裝

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

使用 Carthage 安裝

1.在項目文件夾內創建文件Cartfile,并添加以下內容:github "MagicalPanda/MagicalRecord"

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

carthage update

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

使用 CocoaPods 安裝

使用CocoaPods將 MagicalRecord 也非常簡便:

向 Podfile 中添加一下內容:a.純凈安裝

pod "MagicalRecord"

b.使用 CocoaLumberjack 紀錄日志

pod "MagicalRecord/CocoaLumberjack"

2.進入項目目錄,運行

pod update

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

使用 Xcode 子項目

Xcode 子項目可以將MagicalRecord 作為內在依賴使用。

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.點擊左側項目欄 ,target 欄下選擇需要添加 MagicalRecord 的項目 4.選擇 Build Phases 項并展開 Link Binary With Libraries 欄 5.點擊 + 號添加對應平臺的MagicalRecord framework 6.現在在你的目標源文件中,可以通過 #import添加使用 MagicalRecord。

小貼士

由于iOS平臺 UIKit 不包含 Core Data,如果你在Xcode設置Link Frameworks Automatically 為 NO,你需要手動添加 CoreData.framework。如果是 OS X 平臺,Core Data 已包含在 Cocoa 中,無需再手動添加。

分類方法的簡略表達方式

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

如果你想使用無前綴的分類方法,可以導入以下頭文件:

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

導入頭文件之后,在使用MagicalRecord之前,還需要先調用類方法+[MagicalRecord enableShorthandMethods]:

- (void)theMethodWhereYouSetupMagicalRecord

{

[MagicalRecord enableShorthandMethods];

// 正常設置 MagicalRecord

}

請注意,我們不提供此專題的技術支持。如果無法使用,請提交錯誤,我們將盡快修復。

開始使用

在工程的 PCH預編譯頭文件中導入 MagicalRecord.h 頭文件,既可全局使用。

如果你使用的是 CocoaPods 或者 MagicalRecord.framework,導入代碼如下:

每次調用都會實例化一塊 Core Data 棧,并且對該實例提供 getter 和 setter 方法。 MagicalRecord 將這些實例作為默認使用的棧。

當在DEBUG模式下使用默認SQLite 數據存儲時,如果沒有創建新數據模型就更改了數據模型,MagicalRecord 將會自動刪除舊的存儲并創建新的存儲。這將為你節省大量時間——每當更改數據模型,不必再卸載、重裝應用。但請確保發布的應用的不是DEBUG版本:否則在未告知用戶的情況下刪除應用數據,這可不是好事!

在應用退出之前,需要調用 +cleanUp 方法:

[MagicalRecord cleanUp];

此方法將進行清理工作,清理自定義的錯誤處理,同時將MagicalRecord 創建的 CoreData 棧設為nil。

啟用 iCloud 持久化存儲

要使用蘋果 iCloud Core Data 同步存儲數據,使用下列方法替代上一小節中的方法:

+ (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;

想了解更多內容,請查看Apple’s “iCloud Programming Guide for Core Data”。

提示

如果你需要同時管理多個iCloud 存儲內容,我們推薦使用可以設置 contentNameKey 的方法。不能設置contentNameKey的方法則會根據應用的 bundle identifier(CFBundleIdentifier)自動生成 NSPersistentStoreUbiquitousContentNameKey作為標示。

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

創建新的托管對象上下文

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

+ [NSManagedObjectContext MR_newContext]: 將默認上下文設為父上下文,其并發類型為 NSPrivateQueueConcurrencyType

+ [NSManagedObjectContext MR_newMainQueueContext]: 并發類型為NSMainQueueConcurrencyType

+ [NSManagedObjectContext MR_newPrivateQueueContext]: 并發類型為NSPrivateQueueConcurrencyType.

+ [NSManagedObjectContext MR_newContextWithParent:…]: 可以設置父上下文,并發類型 NSPrivateQueueConcurrencyType.

+ [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 可以為新上下文設置持久化存儲協調器(persistent store coordinator),并發類型 NSPrivateQueueConcurrencyType.

默認上下文(Default Context)

使用 Core Data 時,經常需要處理 NSManagedObject 和 NSManagedObjectContext 對象。

MagicalRecord 提供了便捷的類方法獲取默認托管對象上下文NSManagedObjectContext。獲取的上下文在主線程中執行操作,并可貫穿整個app進行操作,非常適合單線程的簡單應用。

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

通過 MagicalRecord ,這個上下文可以在任何有需要的方法內使用。但是這些方法不能使用上下文參數來指定特定上下文。

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

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];

此方法得到的上下文與默認上下文具有相同的數據結構(object model)和持久化存儲(persistent store),并安全的在其他線程中執行。同時,它的父上下文(parent context)為默認上下文。

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

注意:我們強烈建議在主線程中創建并設置默認上下文,同時將默認上下文的類型設置為NSMainQueueConcurrencyType。

在后臺線程中執行任務

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

更改實體的塊不會在主線程中執行

代碼塊只提供一個 NSManagedObjectContext 對象

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

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

Person *localPerson = [person MR_inContext:localContext];

localPerson.firstName = @"John";

localPerson.lastName = @"Appleseed";

}];

這個方法中,block 將為你提供合適的上下文來執行操作,所以你不必再考慮如何設置上下文了。當提供的上下文完成操作后,將通知默認上下文已更改了實體,并進行更新操作。

如果你想在“保存塊”(save block)執行完成之后進行其他動作,可以使用“完成塊” 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 在主線程(隊列)中執行,所以UI 相關的操作可以放在這個塊中。

創建實體對象

創建并插入新的實體實例到默認上下文(default context)中:

Person *myPerson = [Person MR_createEntity];

創建并將實體插入到指定上下文中:

Person *myPerson = [Person MR_createEntityInContext:otherContext];

刪除實體對象

刪除默認上下文中實體:

[myPerson MR_deleteEntity];

刪除指定上下文中實體:

[myPerson MR_deleteEntityInContext:otherContext];

刪除默認上下文中所有實體:

[Person MR_truncateAll];

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

[Person MR_truncateAllInContext:otherContext];

獲取實體對象

基礎查詢

MagicalRecord 中大多數查詢方法返回 NSArray 類型的結果。

假設我們有一個名為 Person 的實體,且與實體 Department 相關聯。從持久化存儲(persistent store)中查詢所有 Person 實體:

NSArray *people = [Person MR_findAll]; //

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

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

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

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

//按照屬性 LastName 升序,FirstName 升序排序

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

查詢所有Person實體,按照多個屬性排序,指定的多個屬性可以設置不同的排序方式,如果某個屬性未指明排序方式,則按方法中”ascending:”參數進行排序:

//按照屬性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 對象

使用謂詞獲取對應查詢條件的 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];

查詢實體數目

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

//返回類型為NSNumber

NSNumber *count = [Person MR_numberOfEntities];

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

//返回類型為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: 參數指定查詢的上下文:

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];

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

withValue:@"Gump"

inContext:someOtherContext];

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

保存實體

保存操作的時機

大多數情況下,當數據發生變化的時候就執行保存操作。有一些應用只是在應用關閉的時候才保存,但這樣增加了丟失數據的風險。當應用崩潰的時候就會造成數據丟失,用戶會丟失他們的數據。應當避免這種情況的發生。

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

1.在后臺線程中執行保存操作:

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

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

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

// 在此塊中的所有操作均在后臺執行

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

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

2.將任務拆分成小任務保存:

像一次性導入大量數據的任務,你需要將數據拆分成小塊來操作。一次處理的數據大小并沒有統一標準,你需要使用類似 Apples’ Instruments 的工具來測試調整,獲取最佳大小。

處理長時儲存

iOS平臺

當iOS平臺app退出時,app會獲得短暫時間向硬盤中保存整理數據。如果你的應用存儲時間較長,最好在應用完全終止前,請求延長后臺執行時間。

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {

// 此處執行保存操作

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

[application endBackgroundTask:bgTask];

bgTask = UIBackgroundTaskInvalid;

}];

請確保認真閱讀beginBackgroundTaskWithExpirationHandler ,因為不適當或不必要的延長應用生命周期,申請上架的時候可能會被 App Store 拒絕。

OS X 平臺

OS X Mavericks (10.9) 及更高版本中,App Nap 可以讓你 app 看起來像關閉了,但仍在后臺運行。如果有長時間的保存操作,最好的方式是禁用自動終止和突然終止( 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

早期發布的MagicalRecord中,我們提供了獲取當前線程中數據上下文(managed object context )的方法。不幸的是,這個方法已經不能正確獲取當前線程中的上下文了。 Grand Central Dispatch (GCD) 導致一個隊列(queue)可能被分發到多個線程中,而CoreData 使用 GCD 之前,舊方法中使用的是舊的 NSThread API 。如果想了解更多內容,請看 Saul 的文章Why contextForCurrentThread Doesn’t Work in MagicalRecord 。

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

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

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

廢棄方法:

[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {

NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntity];

// …

}];

替代方法:

[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {

NSManagedObject *inserted = [SingleEntityWithNoRelationships MR_createEntityInContext:localContext];

// …

}];

當MagicalRecord 更新到 3.0 版,獲取當前線程上下文的方法會被完全移除。未設置“上下文(context)”的方法,將使用默認上下文——詳情見 MagicalRecord 3.0 版文檔。

MagicalRecord 2.2.0 版本變化

在 MagicalRecord 2.2.0版中, 保存相關的 API 修改的更為統一,同時也更符合Core Data 中的命名方式。同時加入了大量自動測試來確保保存方法,無論新舊,在更新后仍可以使用。

MR_save 方法暫時恢復為在當前線程同步運行,同時保存到持久化存儲中。但在下一個主要版本 (MagicalRecord 3.0)中,將會移除MR_save 方法。如果你和在未來版本的庫保持一致,現在應使用 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;

導入數據(importing data)

我們正在著手更新文檔,更新期間請參考 Cocoa Is My Girlfriend 中的文章 Importing Data Made Easy 。本節大部分內容是參考 Saul 的文章撰寫的。

MagicalRecord 可以直接從標準 NSObject 對象(NSArray、NSDictionary等)導入到Core Data 存儲。

使用MagicalRecord 將數據從外部資源導入到持久化存儲中,需要兩步工作:

1.定義導入的數據與持久化存儲之間的映射關系

2.執行導入數據操作

定義導入映射

外部數據源在質量(quality)和結構(structure)上千差萬別,我們盡可能讓 MagicalRecord 能夠應對各種情形。

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

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

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

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

默認情況下,MagicalRecord 會根據導入數據的鍵(key)的名稱,自動匹配實體(entity)的屬性(attribute)和關系(relationships)。如果數據模型的鍵(key)與屬性、關系名稱相匹配,那么你不需要做任何操作——MagicalRecord便會自動導入該鍵對應的值(value)。

舉個例子,實體有名為 ‘firstName’ 的屬性(attribute),MagicalRecord將假定導入的數據中有同名的鍵(key)——如果同名鍵真的存在,那么將使用 firstName的值,來為實體firstName 屬性賦值。

但多數情況下并沒有那么理想,要導入的數據的鍵(key)和結構(structure)并不能匹配實體的屬性和關系。此時就需要通過設置數據的鍵與實體的屬性之間的映射關系來導入了。

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

Attributes

Key Type Purpose

attributeValueClassName String 待定(TBD)

dateFormat String 待定. 默認格式 yyyy-MM-dd'T'HH:mm:ssz.

mappedKeyName String 指明導入數據的keypath,支持多層級,以 .作為分隔符, 例如location.latitude

mappedKeyName.[0-9] String 備用keypath,當mappedKeyName指定的keypath不存在時使用。語法同上。

useDefaultValueWhenNotPresent Boolean 為true時,如果導入數據值為空,則為此屬性設置默認值。

Entities

Key Type Purpose

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

Relationships

Key Type Purpose

mappedKeyName String 指明導入數據的keypath,支持多層級,以 .作為分隔符, 例如location.latitude

mappedKeyName.[0-9] String 備用keypath,當mappedKeyName指定的keypath不存在時使用。語法同上。

relatedByAttribute String 指明關系(relationship)的目標的屬性(attribute)

type String TBD

導入對象

導入之前你應當充分了解導入數據對象的結構,同時構建與之對應的實體。完成這些之后,再進行數據導入。導入的方式有以下幾種:

依據數據對象自動創建新實例:

NSDictionary *contactInfo = // 此處解析 JSON 或其他數據源

Person *importedPerson = [Person MR_importFromObject:contactInfo];

兩步方法:

NSDictionary *contactInfo = // 此處解析 JSON 或其他數據源

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

[person MR_importValuesForKeysWithObject:contactInfo];

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

+MR_importFromObject:根據已配置的查詢值(lookup value)查找相應對象(即上文中的relatedByAttribute 和 attributeNameID)。它遵循Cocoa導入key-value 的范例,保證安全導入數據。

+MR_importFromObject: 是對實例方法-MR_importValuesForKeysWithObject:和創建對象方法的封裝,返回值為賦值的新對象。

以上兩個方法均為同步執行方法。導入時間長短不一,很可能影響交互體驗,若將所有數據導入工作都放在后臺執行,就不會影響軟件交互了。像前面提到的,MagicalRecord 提供了很多好用的后臺線程 API :

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {

Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext]; //導入操作

}];

導入數組

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

NSArray *arrayOfPeopleData = /// 解析JSON的結果

NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

此方法與+MR_importFromObject:方法類似,也是同步執行方法,所以你想要后臺執行,就得使用前面提到的方法,在后臺塊里執行。

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

實踐

錯誤數據處理

API 方法經常會返回格式錯誤或內容錯誤的數據。最好的處理方法是通過實體類的分類方法處理,有三個方法可供選擇:

Method Purpose

- (BOOL) shouldImport; 在導入數據前調用此方法。如果返回為 NO,則取消向實體導入數據。

- (void) willImport:(id)data; 緊鄰數據導入之前調用。

- (void) didImport:(id)data; 緊鄰數據導入之后調用。

一般來說,如果你嘗試導入所有數據時,發現一些損壞數據,你可能想修復它們。

常見的情景是在導入JSON數據時,常常將數字字符串被錯誤的解析為數字。如果你想確保它們以字符串的類型導入,可以這么做:

@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

更新數據時刪除本地記錄

在后續的導入操作中,有時不僅需要更新記錄,同時要刪除本地存在、遠程數據庫中不存在的數據。此時需要先根據 relatedByAttribute來判斷哪些記錄不在遠程數據庫中,然后將其刪除。

NSArray *arrayOfPeopleData = /// JSON 解析結果

NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

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

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

[Person MR_deleteAllMatchingPredicate:predicate];

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

@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 已經在Core Data 大部分交互中內置了日志紀錄。讀取、保存數據時,如果啟用了日志,錯誤信息將會被打印到控制臺(console)。

在調試(debug)模式下,日志會打印調試信息(MagicalRecordLoggingLevelDebug),在發布(release)模式下,日志則會輸出錯誤信息(MagicalRecordLoggingLevelError)。

可以通過調用[MagicalRecord setLoggingLevel:]方法來配置日志;選擇使用以下幾個日志等級:

MagicalRecordLogLevelOff: 關閉日志

MagicalRecordLoggingLevelError: 記錄所有錯誤

MagicalRecordLoggingLevelWarn: 記錄警告和錯誤信息

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

MagicalRecordLoggingLevelDebug: 記錄所有調試、有用信息、警告和錯誤信息

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

日志默認配置為MagicalRecordLoggingLevelWarn。

CocoaLumberjack

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

其他資料

以下為安裝使用 MagicalRecord 的相關文章:

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

技術支持

MagicalRecord is provided as-is, free of charge。如果需要技術支持,你可以選擇以下途徑:

在 Stackoverflow.com 提出你的問題并添加 MagicalRecord 標簽。只有添加了 MagicalRecord 標簽,核心團隊才能看到你的問題。Stack Overflow 社區可以幫你迅速得到解答,,MagicalRecord is provided as-is, free of charge。 如果社區無法回答你的問題,那我們將嘗試介入并回答你的提問。

如果你發現MagicalRecord中存在 bug, 請在 Github Issues page for MagicalRecord 頁面提交 a support ticket 。我們將以最快速度解決。另外,請不要在 issue tracker 界面提一般問題。Support questions will be closed unanswered

對于個人問題或者需要緊急技術支持,可以 MagicalPanda 獲取咨詢服務

Twitter

關注 twitter @MagicalRecord 獲取 MagicalRecord 最新信息。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容