版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2018.09.29 星期六 |
前言
數(shù)據(jù)是移動(dòng)端的重點(diǎn)關(guān)注對(duì)象,其中有一條就是數(shù)據(jù)存儲(chǔ)。CoreData是蘋(píng)果出的數(shù)據(jù)存儲(chǔ)和持久化技術(shù),面向?qū)ο筮M(jìn)行數(shù)據(jù)相關(guān)存儲(chǔ)。感興趣的可以看下面幾篇文章。
1. iOS CoreData(一)
2. iOS CoreData實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)(二)
3. Core Data詳細(xì)解析(三) —— 一個(gè)簡(jiǎn)單的入門(mén)示例(一)
4. Core Data詳細(xì)解析(四) —— 一個(gè)簡(jiǎn)單的入門(mén)示例(二)
5. Core Data詳細(xì)解析(五) —— 基于多上下文的Core Data簡(jiǎn)單解析示例(一)
6. Core Data詳細(xì)解析(六) —— 基于多上下文的Core Data簡(jiǎn)單解析示例(二)
7. Core Data詳細(xì)解析(七) —— Core Data的輕量級(jí)遷移(一)
8. Core Data詳細(xì)解析(八) —— Core Data的輕量級(jí)遷移(二)
9. Core Data詳細(xì)解析(九) —— MagicalRecord框架之基本概覽(一)
10. Core Data詳細(xì)解析(十) —— MagicalRecord框架之基本使用(一)
Deleting Entities - 刪除實(shí)體
要在默認(rèn)上下文中刪除單個(gè)實(shí)體:
[myPerson MR_deleteEntity];
從指定上下文中刪除實(shí)體:
[myPerson MR_deleteEntityInContext:otherContext];
獲取默認(rèn)上下文中的所有實(shí)體:
[Person MR_truncateAll];
獲取指定上下文中所有實(shí)體:
[Person MR_truncateAllInContext:otherContext];
Fetching Entities - 獲取實(shí)體
1. Basic Finding - 基本查找
MagicalRecord中的大多數(shù)方法都返回NSArray結(jié)果。
例如,如果您有一個(gè)名為Person的實(shí)體與Department實(shí)體相關(guān)(如 Apple's Core Data examples中所示),您可以使用以下方法從持久性存儲(chǔ)中檢索所有Person實(shí)體:
NSArray *people = [Person MR_findAll];
要返回按特定屬性排序的相同實(shí)體:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName"
ascending:YES];
要返回按多個(gè)屬性排序的實(shí)體:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName"
ascending:YES];
返回由具有不同值的多個(gè)屬性排序的結(jié)果。 如果您沒(méi)有為任何屬性提供值,它將默認(rèn)為您在模型中設(shè)置的任何值:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName"
ascending:YES];
// OR
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES"
ascending:NO];
如果您有一種從數(shù)據(jù)存儲(chǔ)中檢索單個(gè)對(duì)象的唯一方法(例如標(biāo)識(shí)符屬性),則可以使用以下方法:
Person *person = [Person MR_findFirstByAttribute:@"FirstName"
withValue:@"Forrest"];
2. Advanced Finding - 高級(jí)查找
如果您想更具體地使用搜索,可以使用謂詞:
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];
NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];
3. Returning an NSFetchRequest - 返回一個(gè)NSFetchRequest
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];
NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
對(duì)于這些單行調(diào)用中的每一個(gè),都會(huì)創(chuàng)建任何排序條件的NSFetchRequest和NSSortDescriptors。
4. Customizing the Request - 自定義請(qǐng)求
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];
5. Find the number of entities - 查找實(shí)體數(shù)量
您還可以在持久性存儲(chǔ)中執(zhí)行特定類(lèi)型的所有實(shí)體的計(jì)數(shù):
NSNumber *count = [Person MR_numberOfEntities];
或者,如果您正在尋找基于謂詞或某些過(guò)濾器的實(shí)體計(jì)數(shù):
NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];
還有一些補(bǔ)充方法可以返回NSUInteger而不是NSNumber實(shí)例:
+ (NSUInteger) MR_countOfEntities;
+ (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context;
+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter;
+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter
inContext:(NSManagedObjectContext *)context;
6. 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"];
7. Finding entities in a specific context - 在指定上下文中查找實(shí)體
所有find,fetch和request方法都有一個(gè)inContext:方法參數(shù),允許您指定要查詢(xún)的managed object context:
NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];
Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName"
withValue:@"Gump"
inContext:someOtherContext];
NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];
Saving Entities - 保存實(shí)體
1. When should I save? - 何時(shí)使用
通常,當(dāng)數(shù)據(jù)發(fā)生變化時(shí),您的應(yīng)用應(yīng)該保存到持久存儲(chǔ)中。 有些應(yīng)用程序選擇在應(yīng)用程序終止時(shí)存儲(chǔ),但在大多數(shù)情況下這不是必需的 - 事實(shí)上,如果您只是在應(yīng)用程序終止時(shí)保存,那么您將面臨數(shù)據(jù)丟失的風(fēng)險(xiǎn)! 如果你的應(yīng)用程序崩潰會(huì)怎么樣? 用戶(hù)將失去他們所做的所有改變 - 這是一種糟糕的經(jīng)歷,并且很容易避免。
如果您發(fā)現(xiàn)保存需要很長(zhǎng)時(shí)間,那么您應(yīng)該考慮做以下幾件事:
- 1) Save in a background thread - 在后臺(tái)線(xiàn)程中保存:
MagicalRecord提供了一個(gè)簡(jiǎn)單,干凈的API,用于對(duì)實(shí)體進(jìn)行更改,然后將它們保存在后臺(tái)線(xiàn)程中 - 例如:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// Do your work to be saved here, against the `localContext` instance
// Everything you do in this block will occur on a background thread
} completion:^(BOOL success, NSError *error) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
- 2)Break the task down into smaller saves - 將任務(wù)分解為較小的保存:像導(dǎo)入大量數(shù)據(jù)這樣的任務(wù)應(yīng)該總是分解成更小的塊。 對(duì)于您應(yīng)該一次性保存多少數(shù)據(jù)沒(méi)有一刀切的規(guī)則,因此您需要使用
Apple's Instruments等工具來(lái)衡量應(yīng)用程序的性能并適當(dāng)調(diào)整。
2. Handling Long-running Saves - 處理長(zhǎng)時(shí)間運(yùn)行的保存
On iOS
當(dāng)應(yīng)用程序在iOS上終止時(shí),會(huì)給它一個(gè)小的機(jī)會(huì)窗口來(lái)整理并將任何數(shù)據(jù)保存到磁盤(pán)。 如果您知道保存操作可能需要一段時(shí)間,最好的方法是請(qǐng)求延長(zhǎng)應(yīng)用程序的到期時(shí)間,如下所示:
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// Do your work to be saved here
} completion:^(BOOL success, NSError *error) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
請(qǐng)務(wù)必仔細(xì)閱讀read the documentation for beginBackgroundTaskWithExpirationHandler的文檔,因?yàn)椴磺‘?dāng)或不必要地延長(zhǎng)應(yīng)用程序的生命周期可能會(huì)使您的應(yīng)用程序被App Store拒絕。
On OS X
在OS X Mavericks(10.9)及更高版本中,App Nap可以使您的應(yīng)用程序在后臺(tái)運(yùn)行時(shí)有效終止。 如果您知道保存操作可能需要一段時(shí)間,最好的方法是暫時(shí)禁用自動(dòng)和突然終止(假設(shè)您的應(yīng)用支持這些功能):
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方法一樣,在應(yīng)用程序中實(shí)現(xiàn)此方法之前,請(qǐng)務(wù)必閱讀read the documentation on NSProcessInfo。
Importing Data - 引入數(shù)據(jù)
MagicalRecord可以幫助將標(biāo)準(zhǔn)NSObject實(shí)例(如NSArray和NSDictionary)中的數(shù)據(jù)直接導(dǎo)入到Core Data存儲(chǔ)中。
使用MagicalRecord將數(shù)據(jù)從外部源導(dǎo)入持久性存儲(chǔ)是一個(gè)兩步過(guò)程:
- 1) 使用您的數(shù)據(jù)模型定義您導(dǎo)入的數(shù)據(jù)如何映射到您的存儲(chǔ)(它幾乎沒(méi)有代碼?。?/li>
- 2) 執(zhí)行數(shù)據(jù)導(dǎo)入
1. Define Your Import - 定義你的引入
來(lái)自外部來(lái)源的數(shù)據(jù)在質(zhì)量和結(jié)構(gòu)上可能會(huì)發(fā)生巨大變化,因此我們已盡最大努力使MagicalRecord的導(dǎo)入流程變得靈活。
MagicalRecord可以從任何符合鍵值編碼(KVC)的對(duì)象導(dǎo)入數(shù)據(jù)。 我們通常會(huì)發(fā)現(xiàn)人們使用NSArray和NSDictionary實(shí)例,但它適用于任何符合KVC標(biāo)準(zhǔn)的NSObject子類(lèi)。
MagicalRecord利用Xcode數(shù)據(jù)建模工具的User Info值,允許配置導(dǎo)入選項(xiàng)和映射,而無(wú)需編輯任何代碼。

供參考:用戶(hù)信息鍵和值保存在
NSDictionary中,該NSDictionary附加到數(shù)據(jù)模型中的每個(gè)實(shí)體,屬性和關(guān)系,并且可以通過(guò)NSEntityDescription實(shí)例上的userInfo方法訪(fǎng)問(wèn)。
Xcode的數(shù)據(jù)建模工具使您可以通過(guò)數(shù)據(jù)模型檢查器的User Info組訪(fǎng)問(wèn)該字典。 編輯數(shù)據(jù)模型時(shí),可以使用Xcode的菜單 - View > Utilities > Show Data Model Inspector或按鍵盤(pán)上的??3。
默認(rèn)情況下,MagicalRecord將自動(dòng)嘗試將屬性和關(guān)系名稱(chēng)與導(dǎo)入數(shù)據(jù)中的鍵匹配。如果模型中的屬性或關(guān)系名稱(chēng)與數(shù)據(jù)中的鍵匹配,則無(wú)需執(zhí)行任何操作 - 將自動(dòng)導(dǎo)入附加到鍵的值。
例如,如果實(shí)體上的屬性名稱(chēng)為'firstName',則MagicalRecord將假定要導(dǎo)入的數(shù)據(jù)中的鍵也將具有'firstName'鍵 - 如果是,則實(shí)體的firstName屬性將設(shè)置為該值您的數(shù)據(jù)中的firstName鍵。
通常,您導(dǎo)入的數(shù)據(jù)中的鍵和結(jié)構(gòu)與實(shí)體的屬性和關(guān)系不匹配。在這種情況下,您需要告訴MagicalRecord如何將導(dǎo)入數(shù)據(jù)的鍵映射到數(shù)據(jù)模型中的正確屬性或關(guān)系。
我們?cè)?code>Core Data中處理的三個(gè)關(guān)鍵對(duì)象中的每一個(gè) - Entities, Attributes and Relationships - 都有可能需要通過(guò)user info鍵指定的選項(xiàng):
Attributes

Entities

Relationships

2. Importing Objects - 引入對(duì)象
要使用MagicalRecord將數(shù)據(jù)導(dǎo)入你的存儲(chǔ),您需要了解兩件事:
- 1) 您要導(dǎo)入的數(shù)據(jù)的格式以及它的方式
MagicalRecord導(dǎo)入背后的基本思想是你知道應(yīng)該導(dǎo)入數(shù)據(jù)的實(shí)體,然后你編寫(xiě)一行代碼將這個(gè)實(shí)體與要導(dǎo)入的數(shù)據(jù)聯(lián)系起來(lái)。 有幾個(gè)選項(xiàng)可以啟動(dòng)導(dǎo)入過(guò)程。
要從對(duì)象自動(dòng)創(chuàng)建新實(shí)例,可以使用以下更短的方法:
NSDictionary *contactInfo = // Result from JSON parser or some other source
Person *importedPerson = [Person MR_importFromObject:contactInfo];
您還可以使用兩階段方法:
NSDictionary *contactInfo = // Result from JSON parser or some other source
Person *person = [Person MR_createEntity]; // This doesn't have to be a new entity
[person MR_importValuesForKeysWithObject:contactInfo];
如果您希望通過(guò)覆蓋其屬性來(lái)更新現(xiàn)有對(duì)象,則兩步方法可能會(huì)有所幫助。
+ MR_importFromObject:將根據(jù)配置的查找值查找現(xiàn)有對(duì)象(請(qǐng)參閱relatedByAttribute和attributeNameID)。 還要注意這是如何遵循在Cocoa中導(dǎo)入鍵值對(duì)列表的內(nèi)置范例,以及遵循導(dǎo)入數(shù)據(jù)的安全方式。
+ MR_importFromObject:類(lèi)方法使用前面提到的-MR_importValuesForKeysWithObject:實(shí)例方法提供創(chuàng)建新對(duì)象的包裝器,并返回填充了數(shù)據(jù)的新創(chuàng)建的對(duì)象。
值得注意的一個(gè)關(guān)鍵項(xiàng)是這兩種方法都是同步的。 雖然某些導(dǎo)入將比其他導(dǎo)入更長(zhǎng)時(shí)間,但仍然強(qiáng)烈建議在后臺(tái)執(zhí)行所有導(dǎo)入,以免影響用戶(hù)交互。 如前所述,MagicalRecord提供了一個(gè)方便的API,使后臺(tái)線(xiàn)程更易于管理:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {
Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext];
}];
3. Importing Arrays - 導(dǎo)入數(shù)組
通常使用JSON數(shù)組提供數(shù)據(jù)列表,或者導(dǎo)入單個(gè)類(lèi)型數(shù)據(jù)的大型列表。 導(dǎo)入此類(lèi)列表的詳細(xì)信息將在+ MR_importFromArray:類(lèi)方法中進(jìn)行處理。
NSArray *arrayOfPeopleData = /// result from JSON parser
NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];
此方法與+ MR_importFromObject:也是同步的,因此對(duì)于后臺(tái)導(dǎo)入,請(qǐng)使用前面提到的輔助方法在后臺(tái)執(zhí)行block塊。
如果您的導(dǎo)入數(shù)據(jù)與您的Core Data模型完全匹配,那么請(qǐng)不要繼續(xù)讀取,因?yàn)槟枰獙⑸鲜龇椒▽?dǎo)入Core Data存儲(chǔ)中。 但是,如果您的數(shù)據(jù)與大多數(shù)數(shù)據(jù)一樣,沒(méi)有什么微小的偏差,請(qǐng)繼續(xù)讀取,因?yàn)槲覀儗⒔榻BMagicalRecord的一些功能,這些功能將幫助您處理幾個(gè)常見(jiàn)的偏差。
4. Best Practice - 最佳實(shí)踐
Handling Bad Data When Importing - 導(dǎo)入時(shí)處理錯(cuò)誤數(shù)據(jù)
API通??梢苑祷馗袷交蛑挡灰恢碌臄?shù)據(jù)。 處理此問(wèn)題的最佳方法是在實(shí)體類(lèi)上使用導(dǎo)入類(lèi)別方法。 提供了三個(gè):

通常,如果您的數(shù)據(jù)不好,您將需要修復(fù)導(dǎo)入在嘗試導(dǎo)入任何值后所執(zhí)行的操作。
常見(jiàn)的情況是導(dǎo)入JSON數(shù)據(jù),其中數(shù)字字符串通常可能被誤解為實(shí)際數(shù)字。 如果要確保將值作為字符串導(dǎo)入,可以執(zhí)行以下操作:
@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
Deleting local records on import update - 在導(dǎo)入更新時(shí)刪除本地記錄
有時(shí),您需要確保后續(xù)導(dǎo)入操作不僅會(huì)更新,還會(huì)刪除未包含在遠(yuǎn)程數(shù)據(jù)集中的本地記錄。 為此,請(qǐng)通過(guò)relatedByAttribute(下例中的id)獲取此更新中未包含的所有本地記錄,并在導(dǎo)入新數(shù)據(jù)集之前立即刪除它們。
NSArray *arrayOfPeopleData = /// result from JSON parser
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)記錄,您可以使用與上面類(lèi)似的邏輯,但在Person的isImport:方法中實(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];
}
如果您還想確保在此更新期間刪除相關(guān)記錄,您可以使用與上面類(lèi)似的邏輯,但在Person的isImport:方法中實(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];
}
Logging - 日志
MagicalRecord在與Core Data的大多數(shù)交互中都內(nèi)置了日志記錄。 在獲取或保存數(shù)據(jù)期間發(fā)生錯(cuò)誤時(shí),會(huì)捕獲這些錯(cuò)誤并(如果已啟用它們)記錄到控制臺(tái)。
日志記錄配置為在調(diào)試版本中默認(rèn)輸出調(diào)試消息(MagicalRecordLoggingLevelDebug),并將在發(fā)布版本中輸出錯(cuò)誤消息(MagicalRecordLoggingLevelError)。
可以通過(guò)調(diào)用[MagicalRecord setLoggingLevel:]來(lái)配置記錄,使用一個(gè)預(yù)定義的日志記錄級(jí)別:
-
MagicalRecordLogLevelOff:不記錄任何內(nèi)容 -
MagicalRecordLoggingLevelError:記錄所有錯(cuò)誤 -
MagicalRecordLoggingLevelWarn:記錄警告和錯(cuò)誤 -
MagicalRecordLoggingLevelInfo:記錄信息,警告和錯(cuò)誤消息 -
MagicalRecordLoggingLevelDebug:記錄所有調(diào)試,信息,警告和錯(cuò)誤消息 -
MagicalRecordLoggingLevelVerbose:記錄詳細(xì)的診斷,信息,警告和錯(cuò)誤消息
日志記錄級(jí)別默認(rèn)為MagicalRecordLoggingLevelWarn。
1. CocoaLumberjack
如果它可用,MagicalRecord會(huì)將其日志指向CocoaLumberjack。 您需要做的就是確保在導(dǎo)入MagicalRecord之前導(dǎo)入了CocoaLumberjack,如下所示:
// Objective-C
#import <CocoaLumberjack/CocoaLumberjack.h>
#import <MagicalRecord/MagicalRecord.h>
// Swift
import CocoaLumberjack
import MagicalRecord
2. Disabling Logging Completely - 完全禁用日志記錄
對(duì)大多數(shù)人來(lái)說(shuō),這是不必要的。 將日志記錄級(jí)別設(shè)置為MagicalRecordLogLevelOff將確保不打印日志。
即使使用MagicalRecordLogLevelOff,也可以在進(jìn)行日志調(diào)用時(shí)執(zhí)行非常快速的檢查。 如果您絕對(duì)需要禁用日志記錄,則需要在編譯MagicalRecord時(shí)定義以下內(nèi)容:
#define MR_LOGGING_DISABLED 1
請(qǐng)注意,這只有在您將MagicalRecord的源添加到您自己的項(xiàng)目中時(shí)才有效。 您也可以將其添加到MagicalRecord項(xiàng)目的OTHER_CFLAGS中,作為-DMR_LOGGING_DISABLED = 1。
其他資源
- 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框架之基本使用,感興趣的給個(gè)贊或者關(guān)注~~~
