Magical Record 全面解析

Magical Record是用來操作Core Data的一個(gè)第三方工具,在介紹Magical Record 之前必須要先了解一下Core Data的基本概念

Core Data基本介紹

Core Data Stack

核心數(shù)據(jù)堆棧是由一個(gè)或多個(gè)與單個(gè)persistent store coordinator關(guān)聯(lián)的managed object contexts 組成,而persistent store coordinator是和一個(gè)或多個(gè)persistent stores關(guān)聯(lián)在一起。堆棧包含了CoreData的所有組件查詢,創(chuàng)建,操作managed objects.
簡單來說包含了:

  • 一個(gè)包含了記錄的persistent store.類似于數(shù)據(jù)庫
  • 一個(gè)在本地?cái)?shù)據(jù)和對象之間的persistent object store
  • 一個(gè)聚合了所有存儲(chǔ)的persistent store coordinator
  • 一個(gè)描述實(shí)體的managed object model
  • 一個(gè)容器包含managed objectsmanaged object context容器

可能有點(diǎn)繞,不過一看圖世界就清晰了

如下圖:


Managed Object

Managed Object是一個(gè)模型對象(模型-視圖-控制器的意義上),它代表了一個(gè)持久存儲(chǔ)的記錄。管理對象是實(shí)例NSManagedObject或子類NSManagedObject。

管理對象有一個(gè)實(shí)體的描述對象,告訴它代表著什么實(shí)體的引用。以這種方式,NSManagedObject可以表示任何實(shí)體不需要每個(gè)實(shí)體的唯一的子類。如果要實(shí)現(xiàn)自定義行為,例如計(jì)算派生屬性值,或者為了實(shí)現(xiàn)驗(yàn)證邏輯可以使用一個(gè)子類。

還是來看圖:

Managed Object Model

Manage Context Object

Manage Context Object代表單個(gè)對象的空間,,在核心數(shù)據(jù)的應(yīng)用程序。管理對象上下文的一個(gè)實(shí)例的NSManagedObjectContext。它的主要職責(zé)是管理管理對象的集合。這些管理對象代表一個(gè)或多個(gè)持久存儲(chǔ)的一個(gè)內(nèi)部一致的看法。上下文是在管理對象的生命周期核心作用。

上下文是在核心數(shù)據(jù)堆棧中的中心對象。這是你用它來創(chuàng)建和獲取管理對象和管理撤消和恢復(fù)操作的對象。內(nèi)的給定范圍內(nèi),有至多一個(gè)被管理目標(biāo)代表在永久存儲(chǔ)器的任何給定的記錄。

上下文被連接到一個(gè)父對象存儲(chǔ)這通常是一個(gè)持久存儲(chǔ)協(xié)調(diào),但可能是另一個(gè)管理對象上下文。當(dāng)你獲取對象,上下文要求其父對象存儲(chǔ)返回那些符合提取請求的對象。您對管理對象的修改,直到您保存的背景下不被提交到父store。

在某些應(yīng)用中,你可能想保持獨(dú)立組來管理對象和編輯這些對象的; 或者你可能需要執(zhí)行使用一個(gè)上下文,同時(shí)允許用戶與另一個(gè)對象交互的后臺(tái)操作

Persistent Store Coordinator

哎!翻譯太累了。直接上圖吧

這張圖把這個(gè)的架構(gòu)解釋得非常清楚

Fetch Request

官方文檔

開始使用Magical Record

導(dǎo)入MagicalRecord.h在項(xiàng)目的預(yù)編譯文件*.pch中。這保證了可以全局訪問所需要的頭文件。

使用了CocoaPods或者MagicalRecord.framework,用如下方式導(dǎo)入:

// Objective-C
#import <MagicalRecord/MagicalRecord.h>
// Swift
import MagicalRecord

如果是把源文件直接放到項(xiàng)目中,則直接#import "MagicalRecord.h"

接下里,在app delegate的某些地方,比如- applicationDidFinishLaunching: withOptions:或者-awakeFromNib,使用下面的某一個(gè)方法來配置MagicalRecord.

+ (void)setupCoreDataStack;
+ (void)setupAutoMigratingCoreDataStack;
+ (void)setupCoreDataStackWithInMemoryStore;
+ (void)setupCoreDataStackWithStoreNamed:(NSString *)storeName;
+ (void)setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;
+ (void)setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL;
+ (void)setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:(NSURL *)storeURL;

每次調(diào)用Core Data的堆棧的實(shí)例,提供給了這些實(shí)例的getter,setter方法。這些實(shí)例被MagicalRecord很好的管理,被識(shí)別為默認(rèn)方式。

當(dāng)通過DEBUG模式標(biāo)識(shí)使用SQLite數(shù)據(jù)庫,不創(chuàng)建新的model版本來改變model將會(huì)引起MagicalRecord自動(dòng)的刪除老的數(shù)據(jù)庫并且自動(dòng)的創(chuàng)建一個(gè)新的。這樣可以節(jié)約很多時(shí)間--不需要每次都卸載重裝app來讓data model改變,確保你的app不是用的DEBUG模式:當(dāng)刪除app數(shù)據(jù)的時(shí)候不告訴用戶真的是一種很糟糕的方式

在你的app退出之前,你應(yīng)該調(diào)用類方法+cleanUp

[MagicalRecord cleanUp];

這將會(huì)清理MagicalRecord,比如自定義的錯(cuò)誤處理,讓通過MagicalRecord創(chuàng)建的Core Data堆棧為nil.

使用Managed Object Contexts

創(chuàng)建新的上下文

一些簡單的類方法用來幫助快速的你創(chuàng)建新的上下文

    • [NSManagedObjectContext MR_newMainQueueContext]:
    • [NSManagedObjectContext MR_newPrivateQueueContext]:
    • [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 允許你具體化persistent store coordinator為新的上下文,有一個(gè)NSPrivateQueueConcurrencyType

默認(rèn)上下文

當(dāng)使用CoreData,你將不斷的和兩個(gè)主要的對象打交道,NSManagedObjectNSManagedObjectContext.

MagicalRecord提高了一個(gè)簡單的類方法來獲取默認(rèn)的NSManagedObjectContext,這個(gè)上下文貫穿了你的app始終,這個(gè)上下文的操作會(huì)在在主線程中進(jìn)行,并且對于單線程的app比較適合

通過如下方式訪問到默認(rèn)的上下文:

NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

這個(gè)上下文將在MagicalRecord任何使用了上下文的方法中使用,但是沒有提供一個(gè)具體的NSManagedObjectContext參數(shù)。

如果你需要?jiǎng)?chuàng)建一個(gè)不再主線程中使員工的上下文,使用:

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];

這種方式將會(huì)創(chuàng)建一個(gè)和default context有相同的對象和persistent store.安全在其他線程使用。它將會(huì)默認(rèn)的將default context作為它的父上下文

如果你想默認(rèn)讓myNewContext實(shí)例化所有的fetch request.使用類方法的方式

[NSManagedObjectContext MR_setDefaultContext:myNewContext];

注意:高度建議default context使用類型為NSMainQueueConcurrencyType的上下文來創(chuàng)建并設(shè)置在主線程。

在后臺(tái)線程中執(zhí)行

MagicalRecord提供了方法來設(shè)置,協(xié)調(diào)上下文在后臺(tái)線程中使用。后臺(tái)保存操作受到了UIView動(dòng)畫使用Block的方式,但也存在了一些不同

  • 在你對實(shí)體進(jìn)行改變了的block,絕對不在主線程中執(zhí)行

  • 單個(gè)的NSManagedObjectContext提供了block使用。

舉個(gè)例子,你有一個(gè)Person實(shí)體,并且需要設(shè)置firstName和lastName,下面的代碼展示了你怎樣通過MagicalRecord來設(shè)置后臺(tái)上下文進(jìn)行使用。

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

  Person *localPerson = [person MR_inContext:localContext];
  localPerson.firstName = @"John";
  localPerson.lastName = @"Appleseed";

}];

在這個(gè)方法,具體的block提供了一個(gè)合適的上下文讓你進(jìn)行操作,不需要擔(dān)心去設(shè)置上下文,以便它告訴default context已經(jīng)做了。并且應(yīng)該更新,因?yàn)槭窃谄渌€程里面改變進(jìn)行的。

當(dāng)執(zhí)行完了saveBlock,你可以在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];

}];

completion block在主線程中被調(diào)用,為了UI更新更安全。

創(chuàng)建實(shí)體

在默認(rèn)的上下文中插入一個(gè)實(shí)體,如下:

Person *myPerson = [Person MR_createEntity];

在具體的上下文中插入一個(gè)實(shí)體
Person *myPerson = [Person MR_createEntityInContext:otherContext];

刪除一個(gè)實(shí)體

在默認(rèn)上下文中刪除:
[myPerson MR_deleteEntity];

在具體上下文中刪除:
[myPerson MR_deleteEntityInContext:otherContext];

截?cái)嗨袑?shí)體在默認(rèn)上下文
[Person MR_truncateAll];

截?cái)嗨袑?shí)體在具體上下文
[Person MR_truncateAllInContext:otherContext];

查詢實(shí)體

基本查找

MagicalRecord大多數(shù)方法是返回一個(gè)NSArray數(shù)組。

舉例,如果你有一個(gè)person實(shí)體和department實(shí)體關(guān)聯(lián),你可以查詢所有的person實(shí)體從persistent store通過如下方式實(shí)現(xiàn):

NSArray *people = [Person MR_findAll];

傳入一個(gè)具體的參數(shù)返回一個(gè)排序后的數(shù)組:

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

傳入多個(gè)具體的參數(shù)返回一個(gè)排序后的數(shù)組

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

傳入多個(gè)不同參數(shù)值得到排序結(jié)果,如果你不提供任何一個(gè)參數(shù)的默認(rèn)值,就會(huì)默認(rèn)使用你在model中的設(shè)置。

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

// OR

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

如果你有一種唯一從數(shù)據(jù)庫中查詢單個(gè)對象的方法(比如作為唯一屬性),你可以通過下面的方法:

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

高級查找

如果想去具體化你的搜索,你可以使用謂詞

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

返回NSFetchRequest

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

關(guān)于每一行的調(diào)用, NSFetchRequestNSSortDescriptor作為排序的標(biāo)配。

自定有Requset

Predicate *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ù)量

可以執(zhí)行所有實(shí)體類型輸血量在persistent store

NSNumber *count = [Person MR_numberOfEntities];

或者基于查詢的數(shù)量
NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];

這里有一組方法來返回NSUInteger而不是NSNumber

+ (NSUInteger) MR_countOfEntities;
+ (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context;
+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter;
+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter 
                                     inContext:(NSManagedObjectContext *)context;

聚合操作

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"];

在具體的上下文中查找實(shí)體

所有的 find, fetch, request方法都有一個(gè)inContext:,方法參數(shù)允許具體使用哪一個(gè)上下文查詢:

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];

Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" 
                                                  withValue:@"Gump" 
                                                  inContext:someOtherContext];

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

存儲(chǔ)實(shí)體

什么時(shí)候應(yīng)該保存

總的來說,當(dāng)數(shù)據(jù)發(fā)生改變的時(shí)候應(yīng)該保存到persistent store(s).一些應(yīng)用選擇在應(yīng)用終止的時(shí)候才保存。然而,在大多數(shù)場景下是不需要的。事實(shí)上,只在應(yīng)用終止的時(shí)候保存,會(huì)有數(shù)據(jù)丟失的風(fēng)險(xiǎn)。萬一你的應(yīng)用崩潰了怎么辦?用戶將丟失他對數(shù)據(jù)所做的改變。那樣的話是一種相當(dāng)糟糕的體驗(yàn),可以簡單的避免。

如果你覺得執(zhí)行保存話費(fèi)了大量的時(shí)間,有幾件事情需要考慮:

  • 1.在后臺(tái)線程保存:MagicalRecord提高了非常簡單的API來讓改變的實(shí)例按順序的在后臺(tái)保存,舉個(gè)例子:
[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.將任務(wù)分解為多個(gè)小任務(wù)保存:比如大量數(shù)據(jù)的導(dǎo)入應(yīng)被分解為小塊,沒有確切的原則來決定一次導(dǎo)入多少數(shù)據(jù),你需要測量你應(yīng)用的性能比如通過蘋果的Instruments和tune.

解決長期運(yùn)行中的保存

在iOS上

當(dāng)應(yīng)用在iOS上終止運(yùn)行,有一個(gè)很小的機(jī)會(huì)去清理,保存數(shù)據(jù)到磁盤。如果你知道保存操作可能會(huì)花一段時(shí)間,最好的方式就是去申請一個(gè)額外的截止時(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;
}];

確保仔細(xì)的讀過[

使用模式

  • 保存數(shù)據(jù)
  • 查找實(shí)體
  • 導(dǎo)入數(shù)據(jù)
  • 線程安全

日志

MagicalRecord建立了在和Core Data交互的時(shí)候的日志。當(dāng)錯(cuò)誤發(fā)生的時(shí)候,這些錯(cuò)誤將會(huì)被捕獲。并且將打印到控制臺(tái)。

日志被配置為輸出調(diào)試信息(MagicalRecordLoggingLevelDebug)在deug編譯的時(shí)候默認(rèn)的,將會(huì)輸出日志信息MagicalRecordLoggingLevelError在realease下。

日志通過[MagicalRecord setLoggingLevel:]配置,使用下面的幾種預(yù)定義的日主等級。

  • MagicalRecordLogLevelOff:不開始日志
  • MagicalRecordLoggingLevelError:記錄所有錯(cuò)誤
  • MagicalRecordLoggingLevelWarn:記錄警告和錯(cuò)誤
  • MagicalRecordLoggingLevelInfo:記錄日志有用的信息,錯(cuò)誤,警告
  • MagicalRecordLoggingLevelDebug:所有調(diào)試信息
  • MagicalRecordLoggingLevelVerbose:日志冗長的診斷信息,有用的信息,錯(cuò)誤,警告

日志默認(rèn)等級是 MagicalRecordLoggingLevelWarn

關(guān)閉日志

大多數(shù)人而言,這個(gè)不需要,設(shè)置日志等級為MagicalRecordLogLevelOff將會(huì)保證不再打印日志信息

甚至當(dāng)使用了MagicalRecordLogLevelOff,快速檢測檢查可能被調(diào)用無論何時(shí)日志被調(diào)用。如果想絕對的關(guān)閉日志。你可以定義如下,當(dāng)編譯MagicalRecord的時(shí)候
#define MR_LOGGING_DISABLED 1

請注意:這個(gè)之后再增加源碼到項(xiàng)目中才會(huì)起作用。你也可以增加MagicalRecord項(xiàng)目的OTHER_CFLAGS為-DMR_LOGGING_DISABLED=1

日志在2.3.0版本有問題,不能正常的顯示到控制器

google到了解決的方法副在下面

For the development branch (version 2.3.0 and higher) of Magical Record logging seems to still not work correctly. When imported like this: pod 'MagicalRecord', :git => 'https://github.com/magicalpanda/MagicalRecord', :branch => 'develop'

I have no logging output on my Xcode console. But I altered the post_install script of the Cocoapod. The following should enable logging: https://gist.github.com/Blackjacx/e5f3d62d611ce435775e

With that buildsetting included in GCC_PREPROCESSOR_DEFINITIONS logging of Magical Record can be controlled in 2.3.0++ by using [MagicalRecord setLoggingLevel:]
  • 腳本:
post_install do |installer|
installer.project.targets.each do |target|
    target.build_configurations.each do |config|
        # Enable the loggin for MagicalRecord
        # https://github.com/magicalpanda/MagicalRecord/wiki/Logging
        if target.name.include? "MagicalRecord"
            preprocessorMacros = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS']
            if preprocessorMacros.nil?
                preprocessorMacros = ["COCOAPODS=1"];
            end
            preprocessorMacros << "MR_LOGGING_ENABLED=1"

config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = preprocessorMacros
end
end
end
end

## 自己嘗試遇到的坑

### 日記記錄

2.3.0版本同樣遇到了日志不能正常輸出到控制臺(tái)的問題,雖然能夠拿到解決問題的腳步,但是自己在taget,buildsetting里面都設(shè)置了還是沒有用。自己對cocopods管理的原理還不是很明白。

### 上下文的坑

``NSManagedObjectContext``這個(gè)類是CoreData里面非常重要的類。它有父上下文和子上下文的概念。經(jīng)過了漫長的爬坑,終于在蘋果官方文檔中找到了關(guān)于它詳細(xì)的介紹。

這里只截取``parent store``這節(jié)來講

> ``Managed object contexts ``有一個(gè)父存儲(chǔ),通過它來檢索數(shù)據(jù),提交改變
> 
> 最開始在iOS5的之前,父存儲(chǔ)一直是``persistent store coordinator``。在iOS5之后。父存儲(chǔ)的類型可以是其他的``Managed object contexts ``。但是最終的根context必須是`persistent store coordinator``。協(xié)調(diào)者提高被管理的對象模型,調(diào)用各種對數(shù)據(jù)庫的請求。
> 
> 如果父存儲(chǔ)是一個(gè)``Managed object contexts ``。查詢,保存的操作是被父存儲(chǔ)來協(xié)調(diào)的而不是``persistent store coordinator``。這種方式有兩個(gè)好處,
>
>* 1.在其他線程中執(zhí)行操作
>* 2.管理廢棄的編輯,比如監(jiān)視窗口、view
> 第一種場景,父上下文能夠通過不同的線程從子中獲得請求,

> * 重點(diǎn)部分:當(dāng)在上下文中保存所做的改變的時(shí)候,改變只會(huì)被提交一次存儲(chǔ),如果有子的上下文,改變將會(huì)推到他的父上下文,改變不會(huì)直接保存到數(shù)據(jù)庫,直到根上下文被保存才會(huì)保存到數(shù)據(jù)庫(根管理對象的上下文的父上下文為空)。除此之外,父上下文在保存之前不會(huì)從子中拉取數(shù)據(jù)的改變。如果你想最后提交數(shù)據(jù)的改變,必須保存子上下文,這樣就可以推到父上下文中。


## 測試代碼

上下文的創(chuàng)建時(shí)通過線程來控制,也就是上下文和線程相關(guān)。``[[NSThread currentThread] threadDictionary];``返回的字典就是處理數(shù)據(jù)方面的。

if ([NSThread isMainThread])
{
return [self MR_defaultContext];
}
else
{
int32_t targetCacheVersionForContext = contextsCacheVersion;

    NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
    NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey];
    NSNumber *currentCacheVersionForContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextCacheVersionKey];

    // 保證兩者同時(shí)存在,或者同時(shí)不存在
    NSAssert((threadContext && currentCacheVersionForContext) || (!threadContext && !currentCacheVersionForContext),
             @"The Magical Record keys should either both be present or neither be present, otherwise we're in an inconsistent state!");
    // 不存在上下文
    if ((threadContext == nil) || (currentCacheVersionForContext == nil) || ((int32_t)[currentCacheVersionForContext integerValue] != targetCacheVersionForContext))
    {
        // 創(chuàng)建新的上下文
        threadContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
        [threadDict setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey];
        [threadDict setObject:[NSNumber numberWithInteger:targetCacheVersionForContext]
                       forKey:kMagicalRecordManagedObjectContextCacheVersionKey];
    }
    return threadContext;
}

在配置的時(shí)候就會(huì)默認(rèn)創(chuàng)建兩種上下文,一個(gè)根上下文,和協(xié)調(diào)者直接通信的,一個(gè)是主線程相關(guān)的默認(rèn)上下文。默認(rèn)上下文是根上下文的子。

* 有必要說一說``MR_saveWithBlock``這個(gè)方法,自己在寫的時(shí)候就犯錯(cuò)了。

開看看實(shí)現(xiàn)
  • (void)MR_saveWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
    {
    NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:self];

    [localContext performBlock:^{
    [localContext MR_setWorkingName:NSStringFromSelector(_cmd)];

      if (block) {
          block(localContext);
      }
      
      [localContext MR_saveWithOptions:MRSaveParentContexts completion:completion];
    

    }];
    }

是在當(dāng)前的上下文中新建子然后通過子去保存,注意這里的保存方法有個(gè)參數(shù)``MRSaveParentContexts ``,會(huì)連同父上下文一起通常,

在保存的方法中有一段:

// Add/remove the synchronous save option from the mask if necessary
MRSaveOptions modifiedOptions = saveOptions;

            if (saveSynchronously)
            {
                modifiedOptions |= MRSaveSynchronously;
            }
            else
            {
                modifiedOptions &= ~MRSaveSynchronously;
            }

            // If we're saving parent contexts, do so
            [[self parentContext] MR_saveWithOptions:modifiedOptions completion:completion];
類似于遞歸調(diào)用,最終會(huì)調(diào)用根上下文,也就是保存到了數(shù)據(jù)庫。

但是在這之前有個(gè)邏輯想到重要。也就是保存的上下文該沒有改變。如果被確定是沒有改變的,那就不會(huì)中保存的邏輯。

 __block BOOL hasChanges = NO;

if ([self concurrencyType] == NSConfinementConcurrencyType)
{
    hasChanges = [self hasChanges];
}
else
{
    [self performBlockAndWait:^{
        hasChanges = [self hasChanges];
    }];
}

if (!hasChanges)
{
    MRLogVerbose(@"NO CHANGES IN ** %@ ** CONTEXT - NOT SAVING", [self MR_workingName]);

    if (completion)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(NO, nil);
        });
    }

    return;
}

最后來一段有問題的代碼。

 // 在默認(rèn)的上下文中創(chuàng)建實(shí)體
Person *person = [Person MR_createEntity];
// 改變person,引起上下文的改變
person.name = @"test";
person.age = @(100);
[[NSManagedObjectContext MR_defaultContext] MR_saveWithBlock:^(NSManagedObjectContext * _Nonnull localContext) {

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

}];

這段代碼不會(huì)保存成功。

因?yàn)樵赻`MR_saveWithBlock``創(chuàng)建一個(gè)繼承自上下文的心的localContext。然而person所做的改變是在默認(rèn)上下文中,也即是localContext的父上下文。判斷是否改變是根據(jù)localContext來判斷的,結(jié)果就是hasChanges為NO。最終導(dǎo)致保存不成功。

那么改變一下就可以了。也即是我們自己來控制保存。
如下:

// 在默認(rèn)的上下文中創(chuàng)建實(shí)體
Person *person = [Person MR_createEntity];
// 改變person,引起上下文的改變
person.name = @"test";
person.age = @(100);
[[NSManagedObjectContext MR_defaultContext] MR_saveWithOptions:MRSaveParentContexts
                                                    completion:^(BOOL contextDidSave, NSError * _Nullable error) {

}];


## 總結(jié):

多看官方文檔,多看三方庫wiki,多總結(jié)。

養(yǎng)成有耐心的習(xí)慣。勿急躁。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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