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 objects
的managed 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
- [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: 允許你具體化
默認(rèn)上下文
當(dāng)使用CoreData
,你將不斷的和兩個(gè)主要的對象打交道,NSManagedObject
和 NSManagedObjectContext.
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)用, NSFetchRequest
和 NSSortDescriptor
作為排序的標(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ì)的讀過[
- beginBackgroundTaskWithExpirationHandler:](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html#//apple_ref/occ/instm/UIApplication/beginBackgroundTaskWithExpirationHandler:),不合適或者不需要的延長應(yīng)用的存活時(shí)間可能讓你的應(yīng)用被拒。
使用模式
- 保存數(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í)慣。勿急躁。