
MagicalRecord是受Ruby on Rails 中 Active Record fetching便捷性的啟發(fā)而成的庫(kù) 。MagicalRecord的目標(biāo)是:
精簡(jiǎn) CoreData 相關(guān)代碼
可以清晰、簡(jiǎn)單、單行獲取數(shù)據(jù)
當(dāng)需要對(duì)請(qǐng)求進(jìn)行優(yōu)化的時(shí)候,仍可以修改NSFetchRequest
文檔
安裝
添加MagicalRecord很簡(jiǎn)單,你可以從下列方式中任選其一:
使用 Carthage 安裝
1.在項(xiàng)目文件夾內(nèi)創(chuàng)建文件Cartfile,并添加以下內(nèi)容:github "MagicalPanda/MagicalRecord"
2.打開終端進(jìn)入項(xiàng)目目錄,運(yùn)行命令
carthage update
3.將Carthage/Build/目錄下的 MagicalRecord.framework 拖拽到需要項(xiàng)目中。
使用 CocoaPods 安裝
使用CocoaPods將 MagicalRecord 也非常簡(jiǎn)便:
向 Podfile 中添加一下內(nèi)容:a.純凈安裝
pod "MagicalRecord"
b.使用 CocoaLumberjack 紀(jì)錄日志
pod "MagicalRecord/CocoaLumberjack"
2.進(jìn)入項(xiàng)目目錄,運(yùn)行
pod update
3.向項(xiàng)目源文件添加 #import就可以開始使用了。
使用 Xcode 子項(xiàng)目
Xcode 子項(xiàng)目可以將MagicalRecord 作為內(nèi)在依賴使用。
1.將Magicalrecord作為 Git 子模型(submodule)添加到項(xiàng)目中,
$ cd MyXcodeProjectFolder
$ git submodule add https://github.com/magicalpanda/MagicalRecord.git Vendor/MagicalRecord
$ git commit -m "Add MagicalRecord submodule"
2.將 Vendor/MagicalRecord/ 目錄下的 MagicalRecord.xcproj 拖拽到你的項(xiàng)目中 3.點(diǎn)擊左側(cè)項(xiàng)目欄 ,target 欄下選擇需要添加 MagicalRecord 的項(xiàng)目 4.選擇 Build Phases 項(xiàng)并展開 Link Binary With Libraries 欄 5.點(diǎn)擊 + 號(hào)添加對(duì)應(yīng)平臺(tái)的MagicalRecord framework 6.現(xiàn)在在你的目標(biāo)源文件中,可以通過 #import添加使用 MagicalRecord。
小貼士
由于iOS平臺(tái) UIKit 不包含 Core Data,如果你在Xcode設(shè)置Link Frameworks Automatically 為 NO,你需要手動(dòng)添加 CoreData.framework。如果是 OS X 平臺(tái),Core Data 已包含在 Cocoa 中,無(wú)需再手動(dòng)添加。
分類方法的簡(jiǎn)略表達(dá)方式
MagicalRecord 提供的分類方法,均以 MR_作前綴。這遵循為防止命名沖突,蘋果建議為分類方法添加前綴。
如果你想使用無(wú)前綴的分類方法,可以導(dǎo)入以下頭文件:

如果你使用的是Swift語(yǔ)言,你需要將頭文件導(dǎo)入到target的 Objective-C bridging header(橋接頭文件)中。
導(dǎo)入頭文件之后,在使用MagicalRecord之前,還需要先調(diào)用類方法+[MagicalRecord enableShorthandMethods]:
- (void)theMethodWhereYouSetupMagicalRecord
{
[MagicalRecord enableShorthandMethods];
// 正常設(shè)置 MagicalRecord
}
請(qǐng)注意,我們不提供此專題的技術(shù)支持。如果無(wú)法使用,請(qǐng)?zhí)峤诲e(cuò)誤,我們將盡快修復(fù)。
開始使用
在工程的 PCH預(yù)編譯頭文件中導(dǎo)入 MagicalRecord.h 頭文件,既可全局使用。
如果你使用的是 CocoaPods 或者 MagicalRecord.framework,導(dǎo)入代碼如下:

每次調(diào)用都會(huì)實(shí)例化一塊 Core Data 棧,并且對(duì)該實(shí)例提供 getter 和 setter 方法。 MagicalRecord 將這些實(shí)例作為默認(rèn)使用的棧。
當(dāng)在DEBUG模式下使用默認(rèn)SQLite 數(shù)據(jù)存儲(chǔ)時(shí),如果沒有創(chuàng)建新數(shù)據(jù)模型就更改了數(shù)據(jù)模型,MagicalRecord 將會(huì)自動(dòng)刪除舊的存儲(chǔ)并創(chuàng)建新的存儲(chǔ)。這將為你節(jié)省大量時(shí)間——每當(dāng)更改數(shù)據(jù)模型,不必再卸載、重裝應(yīng)用。但請(qǐng)確保發(fā)布的應(yīng)用的不是DEBUG版本:否則在未告知用戶的情況下刪除應(yīng)用數(shù)據(jù),這可不是好事!
在應(yīng)用退出之前,需要調(diào)用 +cleanUp 方法:
[MagicalRecord cleanUp];
此方法將進(jìn)行清理工作,清理自定義的錯(cuò)誤處理,同時(shí)將MagicalRecord 創(chuàng)建的 CoreData 棧設(shè)為nil。
啟用 iCloud 持久化存儲(chǔ)
要使用蘋果 iCloud Core Data 同步存儲(chǔ)數(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)容,請(qǐng)查看Apple’s “iCloud Programming Guide for Core Data”。
提示
如果你需要同時(shí)管理多個(gè)iCloud 存儲(chǔ)內(nèi)容,我們推薦使用可以設(shè)置 contentNameKey 的方法。不能設(shè)置contentNameKey的方法則會(huì)根據(jù)應(yīng)用的 bundle identifier(CFBundleIdentifier)自動(dòng)生成 NSPersistentStoreUbiquitousContentNameKey作為標(biāo)示。
使用托管對(duì)象上下文(managed object Context)
創(chuàng)建新的托管對(duì)象上下文
MagicalRecord提供了很多便捷的類方法,幫助你創(chuàng)建托管對(duì)象上下文(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è)置持久化存儲(chǔ)協(xié)調(diào)器(persistent store coordinator),并發(fā)類型 NSPrivateQueueConcurrencyType.
默認(rèn)上下文(Default Context)
使用 Core Data 時(shí),經(jīng)常需要處理 NSManagedObject 和 NSManagedObjectContext 對(duì)象。
MagicalRecord 提供了便捷的類方法獲取默認(rèn)托管對(duì)象上下文NSManagedObjectContext。獲取的上下文在主線程中執(zhí)行操作,并可貫穿整個(gè)app進(jìn)行操作,非常適合單線程的簡(jiǎn)單應(yīng)用。
獲取方法:NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
通過 MagicalRecord ,這個(gè)上下文可以在任何有需要的方法內(nèi)使用。但是這些方法不能使用上下文參數(shù)來指定特定上下文。
如果你需要一個(gè)不在主線程執(zhí)行的上下文,使用此方法:
NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];
此方法得到的上下文與默認(rèn)上下文具有相同的數(shù)據(jù)結(jié)構(gòu)(object model)和持久化存儲(chǔ)(persistent store),并安全的在其他線程中執(zhí)行。同時(shí),它的父上下文(parent context)為默認(rèn)上下文。
如果你想在后臺(tái)上下文 myNewContext中執(zhí)行所有獲取請(qǐng)求(fetch requests),使用:[NSManagedObjectContext MR_setDefaultContext:myNewContext];
注意:我們強(qiáng)烈建議在主線程中創(chuàng)建并設(shè)置默認(rèn)上下文,同時(shí)將默認(rèn)上下文的類型設(shè)置為NSMainQueueConcurrencyType。
在后臺(tái)線程中執(zhí)行任務(wù)
MagicalRecord 提供了創(chuàng)建及使用后臺(tái)線程上下文的方法。后臺(tái)保存操作為代碼塊的形式,和 UIView 動(dòng)畫方法類似,但也有細(xì)微區(qū)別:
更改實(shí)體的塊不會(huì)在主線程中執(zhí)行
代碼塊只提供一個(gè) NSManagedObjectContext 對(duì)象
舉個(gè)例子,假如我們有一個(gè)名為Person 的實(shí)體,需要同時(shí)設(shè)置它的屬性 firstName 和 lastName, 我們可以使用 MagicalRecord 創(chuàng)建后臺(tái)上下文并進(jìn)行保存操作:
Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
Person *localPerson = [person MR_inContext:localContext];
localPerson.firstName = @"John";
localPerson.lastName = @"Appleseed";
}];
這個(gè)方法中,block 將為你提供合適的上下文來執(zhí)行操作,所以你不必再考慮如何設(shè)置上下文了。當(dāng)提供的上下文完成操作后,將通知默認(rèn)上下文已更改了實(shí)體,并進(jìn)行更新操作。
如果你想在“保存塊”(save block)執(zhí)行完成之后進(jìn)行其他動(dòng)作,可以使用“完成塊” 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 在主線程(隊(duì)列)中執(zhí)行,所以UI 相關(guān)的操作可以放在這個(gè)塊中。
創(chuàng)建實(shí)體對(duì)象
創(chuàng)建并插入新的實(shí)體實(shí)例到默認(rèn)上下文(default context)中:
Person *myPerson = [Person MR_createEntity];
創(chuàng)建并將實(shí)體插入到指定上下文中:
Person *myPerson = [Person MR_createEntityInContext:otherContext];
刪除實(shí)體對(duì)象
刪除默認(rèn)上下文中實(shí)體:
[myPerson MR_deleteEntity];
刪除指定上下文中實(shí)體:
[myPerson MR_deleteEntityInContext:otherContext];
刪除默認(rèn)上下文中所有實(shí)體:
[Person MR_truncateAll];
刪除指定上下文中所有實(shí)體:
[Person MR_truncateAllInContext:otherContext];
獲取實(shí)體對(duì)象
基礎(chǔ)查詢
MagicalRecord 中大多數(shù)查詢方法返回 NSArray 類型的結(jié)果。
假設(shè)我們有一個(gè)名為 Person 的實(shí)體,且與實(shí)體 Department 相關(guān)聯(lián)。從持久化存儲(chǔ)(persistent store)中查詢所有 Person 實(shí)體:
NSArray *people = [Person MR_findAll]; //
查詢所有Person實(shí)體,按照指定屬性排序:
//按照屬性 LastName 升序(asceding)排序
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName" ascending:YES];
查詢所有Person實(shí)體,按照多個(gè)屬性排序:
//按照屬性 LastName 升序,F(xiàn)irstName 升序排序
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName" ascending:YES];
查詢所有Person實(shí)體,按照多個(gè)屬性排序,指定的多個(gè)屬性可以設(shè)置不同的排序方式,如果某個(gè)屬性未指明排序方式,則按方法中”ascending:”參數(shù)進(jìn)行排序:
//按照屬性LastName降序、FirstName升序排序
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName"
ascending:YES];
// 等效于
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES"
ascending:NO];
查詢特定屬性值的實(shí)體,使用此方法:
//所有 FirstName 為 Forrest 的實(shí)體
Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];
高級(jí)查詢
使用 謂詞 查詢特定實(shí)體:
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];
NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];
獲取 NSFetchRequest 對(duì)象
使用謂詞獲取對(duì)應(yīng)查詢條件的 NSFetchRequest 對(duì)象
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ù)目
你還可以獲取指定類型的實(shí)體個(gè)數(shù):
//返回類型為NSNumber
NSNumber *count = [Person MR_numberOfEntities];
使用謂詞或其它過濾器查詢實(shí)體數(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;
合計(jì)操作(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í)體
保存操作的時(shí)機(jī)
大多數(shù)情況下,當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候就執(zhí)行保存操作。有一些應(yīng)用只是在應(yīng)用關(guān)閉的時(shí)候才保存,但這樣增加了丟失數(shù)據(jù)的風(fēng)險(xiǎn)。當(dāng)應(yīng)用崩潰的時(shí)候就會(huì)造成數(shù)據(jù)丟失,用戶會(huì)丟失他們的數(shù)據(jù)。應(yīng)當(dāng)避免這種情況的發(fā)生。
如果保存操作花費(fèi)的時(shí)間太長(zhǎng),你可以采取以下措施:
1.在后臺(tái)線程中執(zhí)行保存操作:
MagicalRecord 為更改、保存實(shí)體提供了簡(jiǎn)潔的API。
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// Do your work to be saved here, against the `localContext` instance
// 在此塊中的所有操作均在后臺(tái)執(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 的工具來測(cè)試調(diào)整,獲取最佳大小。
處理長(zhǎng)時(shí)儲(chǔ)存
iOS平臺(tái)
當(dāng)iOS平臺(tái)app退出時(shí),app會(huì)獲得短暫時(shí)間向硬盤中保存整理數(shù)據(jù)。如果你的應(yīng)用存儲(chǔ)時(shí)間較長(zhǎng),最好在應(yīng)用完全終止前,請(qǐng)求延長(zhǎng)后臺(tái)執(zhí)行時(shí)間。
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;
}];
請(qǐng)確保認(rèn)真閱讀beginBackgroundTaskWithExpirationHandler ,因?yàn)椴贿m當(dāng)或不必要的延長(zhǎng)應(yīng)用生命周期,申請(qǐng)上架的時(shí)候可能會(huì)被 App Store 拒絕。
OS X 平臺(tái)
OS X Mavericks (10.9) 及更高版本中,App Nap 可以讓你 app 看起來像關(guān)閉了,但仍在后臺(tái)運(yùn)行。如果有長(zhǎng)時(shí)間的保存操作,最好的方式是禁用自動(dòng)終止和突然終止( 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方法相同,使用前請(qǐng)仔細(xì)閱讀 《NSProcessInfo文檔》 。
MagicalRecord 2.3.0 版本變化
不建議使用 Context For Current Thread
早期發(fā)布的MagicalRecord中,我們提供了獲取當(dāng)前線程中數(shù)據(jù)上下文(managed object context )的方法。不幸的是,這個(gè)方法已經(jīng)不能正確獲取當(dāng)前線程中的上下文了。 Grand Central Dispatch (GCD) 導(dǎo)致一個(gè)隊(duì)列(queue)可能被分發(fā)到多個(gè)線程中,而CoreData 使用 GCD 之前,舊方法中使用的是舊的 NSThread API 。如果想了解更多內(nèi)容,請(qǐng)看 Saul 的文章Why contextForCurrentThread Doesn’t Work in MagicalRecord 。
為了兼容舊版本,2.3.0 版本中仍然有 +MR_contextForCurrentThread 方法。但我們不推薦使用。
特別是不要再+[MagicalRecord saveWithBlock:…] 方法內(nèi)使用 +MR_contextForCurrentThread 方法——返回的上下文很可能是錯(cuò)的!
如果你現(xiàn)在仍然想使用舊方法,請(qǐng)使用可以接受上下文參數(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)前線程上下文的方法會(huì)被完全移除。未設(shè)置“上下文(context)”的方法,將使用默認(rèn)上下文——詳情見 MagicalRecord 3.0 版文檔。
MagicalRecord 2.2.0 版本變化
在 MagicalRecord 2.2.0版中, 保存相關(guān)的 API 修改的更為統(tǒng)一,同時(shí)也更符合Core Data 中的命名方式。同時(shí)加入了大量自動(dòng)測(cè)試來確保保存方法,無(wú)論新舊,在更新后仍可以使用。
MR_save 方法暫時(shí)恢復(fù)為在當(dāng)前線程同步運(yùn)行,同時(shí)保存到持久化存儲(chǔ)中。但在下一個(gè)主要版本 (MagicalRecord 3.0)中,將會(huì)移除MR_save 方法。如果你和在未來版本的庫(kù)保持一致,現(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)
我們正在著手更新文檔,更新期間請(qǐng)參考 Cocoa Is My Girlfriend 中的文章 Importing Data Made Easy 。本節(jié)大部分內(nèi)容是參考 Saul 的文章撰寫的。
MagicalRecord 可以直接從標(biāo)準(zhǔn) NSObject 對(duì)象(NSArray、NSDictionary等)導(dǎo)入到Core Data 存儲(chǔ)。
使用MagicalRecord 將數(shù)據(jù)從外部資源導(dǎo)入到持久化存儲(chǔ)中,需要兩步工作:
1.定義導(dǎo)入的數(shù)據(jù)與持久化存儲(chǔ)之間的映射關(guān)系
2.執(zhí)行導(dǎo)入數(shù)據(jù)操作
定義導(dǎo)入映射
外部數(shù)據(jù)源在質(zhì)量(quality)和結(jié)構(gòu)(structure)上千差萬(wàn)別,我們盡可能讓 MagicalRecord 能夠應(yīng)對(duì)各種情形。
MagicalRecord 可以從任何遵守鍵值編碼(KVC)的對(duì)象導(dǎo)入數(shù)據(jù)。 大家通常只使用 NSArray 和 NSDictionary 對(duì)象作為數(shù)據(jù)源,實(shí)際上MagicalRecord 適用于任何兼容KVC的 NSObject 子類。
MagicalRecord 利用 Xcode數(shù)據(jù)建模工具“User Info” 的值,無(wú)需更改代碼即可配置導(dǎo)入選項(xiàng)(import option)和映射(mapping)。

供參考:user info 的鍵值以字典(NSDictionary)的形式附加到數(shù)據(jù)模型的實(shí)體(entity)、屬性(attribute)和關(guān)系(relationship)中,可以通過 NSEntityDescription 對(duì)象的 userInfo 方法對(duì)其訪問。
在 Xcode 數(shù)據(jù)建模工具的幫助下,可以直接通過數(shù)據(jù)模型(Data Model)的 Inspector 窗口中的“User Info”更改這個(gè)字典。編輯數(shù)據(jù)模型時(shí),可以通過 View > Utilities > Show Data Model Inspector 或按 ?+?+3 打開 Inspector 窗口。
默認(rèn)情況下,MagicalRecord 會(huì)根據(jù)導(dǎo)入數(shù)據(jù)的鍵(key)的名稱,自動(dòng)匹配實(shí)體(entity)的屬性(attribute)和關(guān)系(relationships)。如果數(shù)據(jù)模型的鍵(key)與屬性、關(guān)系名稱相匹配,那么你不需要做任何操作——MagicalRecord便會(huì)自動(dòng)導(dǎo)入該鍵對(duì)應(yīng)的值(value)。
舉個(gè)例子,實(shí)體有名為 ‘firstName’ 的屬性(attribute),MagicalRecord將假定導(dǎo)入的數(shù)據(jù)中有同名的鍵(key)——如果同名鍵真的存在,那么將使用 firstName的值,來為實(shí)體firstName 屬性賦值。
但多數(shù)情況下并沒有那么理想,要導(dǎo)入的數(shù)據(jù)的鍵(key)和結(jié)構(gòu)(structure)并不能匹配實(shí)體的屬性和關(guān)系。此時(shí)就需要通過設(shè)置數(shù)據(jù)的鍵與實(shí)體的屬性之間的映射關(guān)系來導(dǎo)入了。
Core Data 中我們需要處理的三個(gè)對(duì)象——實(shí)體, 屬性和關(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,支持多層級(jí),以 .作為分隔符, 例如location.latitude
mappedKeyName.[0-9] String 備用keypath,當(dāng)mappedKeyName指定的keypath不存在時(shí)使用。語(yǔ)法同上。
useDefaultValueWhenNotPresent Boolean 為true時(shí),如果導(dǎo)入數(shù)據(jù)值為空,則為此屬性設(shè)置默認(rèn)值。
Entities
Key Type Purpose
relatedByAttribute String 指明連接兩個(gè)實(shí)體的關(guān)系(relationship)的目標(biāo)實(shí)體的屬性(attribute)
Relationships
Key Type Purpose
mappedKeyName String 指明導(dǎo)入數(shù)據(jù)的keypath,支持多層級(jí),以 .作為分隔符, 例如location.latitude
mappedKeyName.[0-9] String 備用keypath,當(dāng)mappedKeyName指定的keypath不存在時(shí)使用。語(yǔ)法同上。
relatedByAttribute String 指明關(guān)系(relationship)的目標(biāo)的屬性(attribute)
type String TBD
導(dǎo)入對(duì)象
導(dǎo)入之前你應(yīng)當(dāng)充分了解導(dǎo)入數(shù)據(jù)對(duì)象的結(jié)構(gòu),同時(shí)構(gòu)建與之對(duì)應(yīng)的實(shí)體。完成這些之后,再進(jìn)行數(shù)據(jù)導(dǎo)入。導(dǎo)入的方式有以下幾種:
依據(jù)數(shù)據(jù)對(duì)象自動(dòng)創(chuàng)建新實(shí)例:
NSDictionary *contactInfo = // 此處解析 JSON 或其他數(shù)據(jù)源
Person *importedPerson = [Person MR_importFromObject:contactInfo];
兩步方法:
NSDictionary *contactInfo = // 此處解析 JSON 或其他數(shù)據(jù)源
Person *person = [Person MR_createEntity]; // 此處不一定是新建實(shí)體,也可使用已有實(shí)體
[person MR_importValuesForKeysWithObject:contactInfo];
兩步方法中的實(shí)體可以是新建的,也可以是已存在的,兩步方法可以幫你更新已存在的實(shí)體。
+MR_importFromObject:根據(jù)已配置的查詢值(lookup value)查找相應(yīng)對(duì)象(即上文中的relatedByAttribute 和 attributeNameID)。它遵循Cocoa導(dǎo)入key-value 的范例,保證安全導(dǎo)入數(shù)據(jù)。
+MR_importFromObject: 是對(duì)實(shí)例方法-MR_importValuesForKeysWithObject:和創(chuàng)建對(duì)象方法的封裝,返回值為賦值的新對(duì)象。
以上兩個(gè)方法均為同步執(zhí)行方法。導(dǎo)入時(shí)間長(zhǎng)短不一,很可能影響交互體驗(yàn),若將所有數(shù)據(jù)導(dǎo)入工作都放在后臺(tái)執(zhí)行,就不會(huì)影響軟件交互了。像前面提到的,MagicalRecord 提供了很多好用的后臺(tái)線程 API :
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {
Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext]; //導(dǎo)入操作
}];
導(dǎo)入數(shù)組
使用 array 對(duì)象來保存單一類型數(shù)據(jù)很常見,可以使用 +MR_importFromArray: 方法來處理:
NSArray *arrayOfPeopleData = /// 解析JSON的結(jié)果
NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];
此方法與+MR_importFromObject:方法類似,也是同步執(zhí)行方法,所以你想要后臺(tái)執(zhí)行,就得使用前面提到的方法,在后臺(tái)塊里執(zhí)行。
如果你導(dǎo)入的數(shù)據(jù)與Core Data model 完全匹配,那么上面的方法已經(jīng)能幫你完成導(dǎo)入工作了。但大多數(shù)情況下并不理想,兩者之間稍有差別, MagicalRecord 的一些方法處理導(dǎo)入數(shù)據(jù)和Core Data model之間的不同。
實(shí)踐
錯(cuò)誤數(shù)據(jù)處理
API 方法經(jīng)常會(huì)返回格式錯(cuò)誤或內(nèi)容錯(cuò)誤的數(shù)據(jù)。最好的處理方法是通過實(shí)體類的分類方法處理,有三個(gè)方法可供選擇:
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ù)時(shí),發(fā)現(xiàn)一些損壞數(shù)據(jù),你可能想修復(fù)它們。
常見的情景是在導(dǎo)入JSON數(shù)據(jù)時(shí),常常將數(shù)字字符串被錯(cuò)誤的解析為數(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ù)時(shí)刪除本地記錄
在后續(xù)的導(dǎo)入操作中,有時(shí)不僅需要更新記錄,同時(shí)要?jiǎng)h除本地存在、遠(yuǎn)程數(shù)據(jù)庫(kù)中不存在的數(shù)據(jù)。此時(shí)需要先根據(jù) relatedByAttribute來判斷哪些記錄不在遠(yuǎn)程數(shù)據(jù)庫(kù)中,然后將其刪除。
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:方法中實(shí)現(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ù)時(shí),如果啟用了日志,錯(cuò)誤信息將會(huì)被打印到控制臺(tái)(console)。
在調(diào)試(debug)模式下,日志會(huì)打印調(diào)試信息(MagicalRecordLoggingLevelDebug),在發(fā)布(release)模式下,日志則會(huì)輸出錯(cuò)誤信息(MagicalRecordLoggingLevelError)。
可以通過調(diào)用[MagicalRecord setLoggingLevel:]方法來配置日志;選擇使用以下幾個(gè)日志等級(jí):
MagicalRecordLogLevelOff: 關(guān)閉日志
MagicalRecordLoggingLevelError: 記錄所有錯(cuò)誤
MagicalRecordLoggingLevelWarn: 記錄警告和錯(cuò)誤信息
MagicalRecordLoggingLevelInfo: 記錄有用信息,警告和錯(cuò)誤信息
MagicalRecordLoggingLevelDebug: 記錄所有調(diào)試、有用信息、警告和錯(cuò)誤信息
MagicalRecordLoggingLevelVerbose: 記錄診斷信息,有用信息、錯(cuò)誤和警告
日志默認(rèn)配置為MagicalRecordLoggingLevelWarn。
CocoaLumberjack
如果你的項(xiàng)目支持 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)簽,核心團(tuán)隊(duì)才能看到你的問題。Stack Overflow 社區(qū)可以幫你迅速得到解答,,MagicalRecord is provided as-is, free of charge。 如果社區(qū)無(wú)法回答你的問題,那我們將嘗試介入并回答你的提問。
如果你發(fā)現(xiàn)MagicalRecord中存在 bug, 請(qǐng)?jiān)?Github Issues page for MagicalRecord 頁(yè)面提交 a support ticket 。我們將以最快速度解決。另外,請(qǐng)不要在 issue tracker 界面提一般問題。Support questions will be closed unanswered
對(duì)于個(gè)人問題或者需要緊急技術(shù)支持,可以 MagicalPanda 獲取咨詢服務(wù)
關(guān)注 twitter @MagicalRecord 獲取 MagicalRecord 最新信息。
開源地址:https://github.com/magicalpanda/MagicalRecord