Magical Record 全面解析

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

Core Data基本介紹

Core Data Stack

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

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

可能有點繞瞭稼,不過一看圖世界就清晰了

如下圖:


Managed Object

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

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

還是來看圖:

Managed Object Model

Manage Context Object

Manage Context Object代表單個對象的空間,沙峻,在核心數(shù)據(jù)的應用程序睦授。管理對象上下文的一個實例的NSManagedObjectContext。它的主要職責是管理管理對象的集合摔寨。這些管理對象代表一個或多個持久存儲的一個內(nèi)部一致的看法去枷。上下文是在管理對象的生命周期核心作用。

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

上下文被連接到一個父對象存儲逗余。這通常是一個持久存儲協(xié)調(diào),但可能是另一個管理對象上下文季惩。當你獲取對象录粱,上下文要求其父對象存儲返回那些符合提取請求的對象腻格。您對管理對象的修改,直到您保存的背景下不被提交到父store啥繁。

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

Persistent Store Coordinator

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

這張圖把這個的架構解釋得非常清楚

Fetch Request

官方文檔

開始使用Magical Record

導入MagicalRecord.h在項目的預編譯文件*.pch中脂凶。這保證了可以全局訪問所需要的頭文件宪睹。

使用了CocoaPods或者MagicalRecord.framework,用如下方式導入:

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

如果是把源文件直接放到項目中蚕钦,則直接#import "MagicalRecord.h"

接下里亭病,在app delegate的某些地方,比如- applicationDidFinishLaunching: withOptions:或者-awakeFromNib,使用下面的某一個方法來配置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的堆棧的實例嘶居,提供給了這些實例的getter,setter方法罪帖。這些實例被MagicalRecord很好的管理,被識別為默認方式邮屁。

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

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

[MagicalRecord cleanUp];

這將會清理MagicalRecord,比如自定義的錯誤處理,讓通過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為新的上下文芋忿,有一個NSPrivateQueueConcurrencyType

默認上下文

當使用CoreData,你將不斷的和兩個主要的對象打交道炸客,NSManagedObjectNSManagedObjectContext.

MagicalRecord提高了一個簡單的類方法來獲取默認的NSManagedObjectContext,這個上下文貫穿了你的app始終,這個上下文的操作會在在主線程中進行戈钢,并且對于單線程的app比較適合痹仙。

通過如下方式訪問到默認的上下文:

NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

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

如果你需要創(chuàng)建一個不再主線程中使員工的上下文开仰,使用:

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];

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

如果你想默認讓myNewContext實例化所有的fetch request.使用類方法的方式

[NSManagedObjectContext MR_setDefaultContext:myNewContext];

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

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

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

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

  • 單個的NSManagedObjectContext提供了block使用鞍恢。

舉個例子傻粘,你有一個Person實體每窖,并且需要設置firstName和lastName,下面的代碼展示了你怎樣通過MagicalRecord來設置后臺上下文進行使用。

Person *person = ...;

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

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

}];

在這個方法弦悉,具體的block提供了一個合適的上下文讓你進行操作窒典,不需要擔心去設置上下文,以便它告訴default context已經(jī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)建實體

在默認的上下文中插入一個實體,如下:

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大多數(shù)方法是返回一個NSArray數(shù)組良拼。

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

NSArray *people = [Person MR_findAll];

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

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

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

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

傳入多個不同參數(shù)值得到排序結果庸推,如果你不提供任何一個參數(shù)的默認值常侦,就會默認使用你在model中的設置。

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

// OR

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

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

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

關于每一行的調(diào)用, NSFetchRequestNSSortDescriptor作為排序的標配掖蛤。

自定有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ù)量

可以執(zhí)行所有實體類型輸血量在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"];

在具體的上下文中查找實體

所有的 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ù)據(jù)發(fā)生改變的時候應該保存到persistent store(s).一些應用選擇在應用終止的時候才保存糖荒。然而诫舅,在大多數(shù)場景下是不需要的却舀。事實上枚尼,只在應用終止的時候保存成畦,會有數(shù)據(jù)丟失的風險苞俘。萬一你的應用崩潰了怎么辦幌甘?用戶將丟失他對數(shù)據(jù)所做的改變硼控。那樣的話是一種相當糟糕的體驗港柜,可以簡單的避免请契。

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

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

解決長期運行中的保存

在iOS上

當應用在iOS上終止運行畔柔,有一個很小的機會去清理氯夷,保存數(shù)據(jù)到磁盤。如果你知道保存操作可能會花一段時間靶擦,最好的方式就是去申請一個額外的截止時間腮考。比如:

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

確保仔細的讀過[

使用模式

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

日志

MagicalRecord建立了在和Core Data交互的時候的日志。當錯誤發(fā)生的時候踩蔚,這些錯誤將會被捕獲棚放。并且將打印到控制臺。

日志被配置為輸出調(diào)試信息(MagicalRecordLoggingLevelDebug)在deug編譯的時候默認的馅闽,將會輸出日志信息MagicalRecordLoggingLevelError在realease下飘蚯。

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

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

日志默認等級是 MagicalRecordLoggingLevelWarn

關閉日志

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

甚至當使用了MagicalRecordLogLevelOff撕捍,快速檢測檢查可能被調(diào)用無論何時日志被調(diào)用拿穴。如果想絕對的關閉日志。你可以定義如下忧风,當編譯MagicalRecord的時候
#define MR_LOGGING_DISABLED 1

請注意:這個之后再增加源碼到項目中才會起作用默色。你也可以增加MagicalRecord項目的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版本同樣遇到了日志不能正常輸出到控制臺的問題狮腿,雖然能夠拿到解決問題的腳步腿宰,但是自己在taget,buildsetting里面都設置了還是沒有用。自己對cocopods管理的原理還不是很明白缘厢。

### 上下文的坑

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

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

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

> * 重點部分:當在上下文中保存所做的改變的時候殷勘,改變只會被提交一次存儲此再,如果有子的上下文,改變將會推到他的父上下文玲销,改變不會直接保存到數(shù)據(jù)庫输拇,直到根上下文被保存才會保存到數(shù)據(jù)庫(根管理對象的上下文的父上下文為空)。除此之外贤斜,父上下文在保存之前不會從子中拉取數(shù)據(jù)的改變策吠。如果你想最后提交數(shù)據(jù)的改變,必須保存子上下文瘩绒,這樣就可以推到父上下文中猴抹。


## 測試代碼

上下文的創(chuàng)建時通過線程來控制,也就是上下文和線程相關锁荔。``[[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];

    // 保證兩者同時存在,或者同時不存在
    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;
}

在配置的時候就會默認創(chuàng)建兩種上下文阳堕,一個根上下文跋理,和協(xié)調(diào)者直接通信的,一個是主線程相關的默認上下文恬总。默認上下文是根上下文的子薪介。

* 有必要說一說``MR_saveWithBlock``這個方法,自己在寫的時候就犯錯了越驻。

開看看實現(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];
    

    }];
    }

是在當前的上下文中新建子然后通過子去保存汁政,注意這里的保存方法有個參數(shù)``MRSaveParentContexts ``,會連同父上下文一起通常缀旁,

在保存的方法中有一段:

// 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)用记劈,最終會調(diào)用根上下文,也就是保存到了數(shù)據(jù)庫并巍。

但是在這之前有個邏輯想到重要目木。也就是保存的上下文該沒有改變。如果被確定是沒有改變的懊渡,那就不會中保存的邏輯刽射。

 __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;
}

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

 // 在默認的上下文中創(chuàng)建實體
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) {

}];

這段代碼不會保存成功誓禁。

因為在``MR_saveWithBlock``創(chuàng)建一個繼承自上下文的心的localContext懈息。然而person所做的改變是在默認上下文中,也即是localContext的父上下文摹恰。判斷是否改變是根據(jù)localContext來判斷的辫继,結果就是hasChanges為NO。最終導致保存不成功俗慈。

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

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

}];


## 總結:

多看官方文檔炮车,多看三方庫wiki,多總結酣溃。

養(yǎng)成有耐心的習慣示血。勿急躁。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末救拉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瘫拣,更是在濱河造成了極大的恐慌亿絮,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麸拄,死亡現(xiàn)場離奇詭異派昧,居然都是意外死亡,警方通過查閱死者的電腦和手機拢切,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門蒂萎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淮椰,你說我怎么就攤上這事五慈。” “怎么了主穗?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵泻拦,是天一觀的道長。 經(jīng)常有香客問我忽媒,道長争拐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任晦雨,我火速辦了婚禮架曹,結果婚禮上隘冲,老公的妹妹穿的比我還像新娘。我一直安慰自己绑雄,他們只是感情好展辞,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绳慎,像睡著了一般纵竖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杏愤,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天靡砌,我揣著相機與錄音,去河邊找鬼珊楼。 笑死通殃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的厕宗。 我是一名探鬼主播画舌,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼已慢!你這毒婦竟也來了曲聂?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤佑惠,失蹤者是張志新(化名)和其女友劉穎朋腋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膜楷,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡旭咽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赌厅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穷绵。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖特愿,靈堂內(nèi)的尸體忽然破棺而出仲墨,到底是詐尸還是另有隱情,我是刑警寧澤揍障,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布宗收,位于F島的核電站,受9級特大地震影響亚兄,放射性物質發(fā)生泄漏混稽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匈勋。 院中可真熱鬧礼旅,春花似錦、人聲如沸洽洁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饿自。三九已至汰翠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昭雌,已是汗流浹背复唤。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烛卧,地道東北人佛纫。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像总放,于是被迫代替她去往敵國和親呈宇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

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