CoreData與MagicalRecord的故事

作者:luhui CVTE iOS開發(fā)工程師

前言

最近使用Core data在多線程下遇到了不少的坑,多數(shù)都是因為使用不規(guī)范所引起的,這里將從最常用的MOC做展開照激,談談多線程下如何正確的使用context。
p.s.這篇博客會持續(xù)不斷的更新鳖孤,主要是記錄core data中踩到的坑,在踩坑中慢慢成長抡笼,不斷規(guī)范core data的使用方法苏揣。

NSManagedObjectContext

定義

說到MOC,當然先要貼上一副描述

An instance of NSManagedObjectContext represents a single “object space” or scratch pad in an application. Its primary responsibility is to manage a collection of managed objects. These objects form a group of related model objects that represent an internally consistent view of one or more persistent stores. A single managed object instance exists in one and only one context, but multiple copies of an object can exist in different contexts. Thus object uniquing is scoped to a particular context.

從字義上來說推姻,他就是一個數(shù)據(jù)庫的上下文平匈,并且維持在內存中。
context可以創(chuàng)建多個,說明一個進程中增炭,可以擁有多個數(shù)據(jù)庫的內存副本忍燥,并且每個副本之間都是相對獨立的。
其實從這一點來看隙姿,Core data的設計結構和git是相似的梅垄。persistent store類似git的中心倉庫,每個moc都是git的分支输玷,moc內的操作都互不影響队丝,只有在save(git的push)時才會保存到persistent store(git中心倉庫)中。

生命周期

The context is a powerful object with a central role in the life-cycle of managed objects, with responsibilities from life-cycle management (including faulting) to validation, inverse relationship handling, and undo/redo. Through a context you can retrieve or “fetch” objects from a persistent store, make changes to those objects, and then either discard the changes or—again through the context—commit them back to the persistent store. The context is responsible for watching for changes in its objects and maintains an undo manager so you can have finer-grained control over undo and redo. You can insert new objects and delete ones you have fetched, and commit these modifications to the persistent store.

All objects fetched from an external store are registered in a context together with a global identifier (an instance of NSManagedObjectID) that’s used to uniquely identify each object to the external store.

作為對象的管理者欲鹏,moc管理著所有注冊在這個moc下的對象的生命周期(包括faulting)机久。并且提供了非常豐富的數(shù)據(jù)操作,基本的增查刪改赔嚎,還提供了了undo/redo的方法膘盖,極大的簡化了開發(fā)時間。

fault

moc中的所有object都是lazy loading的尤误,所有注冊到moc的object一開始都是出于faulted的狀態(tài)侠畔,只有在引用到object時才會真正從數(shù)據(jù)庫中加載數(shù)據(jù)至內存中,這個過程叫fire fault损晤。當一個object被從moc中移除后软棺,他會被置為fault狀態(tài)。

重頭戲:Parent Store

parent store是用來告訴MOC沉馆,數(shù)據(jù)的來源码党,數(shù)據(jù)的讀取保存操作最終都是在parent store中執(zhí)行的德崭。
在OSX10.7以及iOS5.0時代斥黑,Parent Store一直是persisten store coordinator
從OSX10.7以及iOS5.0之后,Parent Store就可以是context眉厨,相當于定義了一個parent 這個moc的數(shù)據(jù)操作最終只會影響到其parent context锌奴,這極大的簡化了不同context之間數(shù)據(jù)的更新。在這之前一直只能用Notification的方式:

[[NSNotificationCenter defaultCenter] addObserver:self
                                      selector:@selector(<#Selector name#>)
                                      name:NSManagedObjectContextDidSaveNotification
                                      object:<#A managed object context#>];

我們可以在程序運行的斷點中查看context的parent store

img
img

這個parent store最終的應用場景是怎樣呢憾股?我們在后邊的Nested context中介紹

MagicalRecord概述

Magical Recrod是git的一個第三方庫鹿蜀,是一個對Core data數(shù)據(jù)操作的封裝庫,常用的core data操作都得到了極大的簡化服球,是在使用core data時提高編程效率的不二之選茴恰。
除開對操作的封裝之外,MR的另一大好處就是為我們封裝了多線程之下的context

+ (NSManagedObjectContext *) MR_contextForCurrentThread;
{
    if ([NSThread isMainThread])
    {
        return [self MR_defaultContext];
    }
    else
    {
        NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
        NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey];
        if (threadContext == nil)
        {
            threadContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
            [threadDict setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey];
        }
        return threadContext;
    }
}

從他的代碼斩熊,可以很清楚的知道往枣,MR為每個線程都創(chuàng)建了context,并且在線程的threadDictionary中維持context的引用。因此在線程回收之前分冈,我們都可以放心的在這個線程中使用context圾另,并且不用擔心何時需要釋放context的問題。
在MR中雕沉,初始化core data時集乔,他的數(shù)據(jù)結構是這樣的:

+ (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinator *)coordinator;
{
    if (defaultManagedObjectContext_ == nil)
    {
        NSManagedObjectContext *rootContext = [self MR_contextWithStoreCoordinator:coordinator];
        [self MR_setRootSavingContext:rootContext];
        NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
        [self MR_setDefaultContext:defaultContext];
        [defaultContext setParentContext:rootContext];
    }
}

一個root context,所有的context都會是這個root context的child context坡椒。
在主線程創(chuàng)建一個default context扰路,其parent context是root context,因此我們在使用core data時肠牲,在主線程下的操作都是用的是default context幼衰,最終的操作都會是先保存到root context中,再保存到數(shù)據(jù)庫中缀雳。
具體的解釋暫且不表渡嚣,放在另一篇文章中詳細解釋使用MR時的一些操作事項

多線程下的Core Data

MOC的三種類型

Confinement (NSConfinementConcurrencyType)
默認狀態(tài),只能在創(chuàng)建的線程中使用肥印,其他線程均不能使用识椰。官方的API文檔寫到:
You can only use this concurrency type if the managed object context’s parent store is a persistent store coordinator.
Private queue (NSPrivateQueueConcurrencyType)
用在多線程下。MR中的rootContext與threadContext均是這個類型
Main queue (NSMainQueueConcurrencyType)
用在主線程下深碱。MR中的defaultContext是這個類型腹鹉。這個類型的context多用在controller以及一些UI對象上,這些對象只能要求在主線程中操作敷硅,因此只能用此類型的context在主線程中操作功咒。

使用Magical Recrod時,多線程下應注意的點

  1. 在不同的線程绞蹦,可以使用任意的context進行讀的操作力奋,但是寫的操作必須保證所有的object均處于同一個context,并且該context所維護的thread就是當前線程幽七。否則在寫的時候會出現(xiàn)概率性的崩潰景殷。一個context不能同時被兩個線程訪問
  2. 不同線程之間的object傳遞使用objectWithId。在MR中澡屡,使用的是existingObjectWithId猿挚,也就是已經在context中注冊的object才可以拿到
    NSManagedObjectContext *moc = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
    Department *d = [Department MR_createInContext:moc];
    NSManagedObjectContext *moc2 = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
    Department *t = [d MR_inContext:moc2];
    Department *t2 = (Department *)[moc2 objectWithID:d.objectID];
    NSLog(@"t:%@ and t2:%@", t, t2);

這個代碼的輸出結果為

t:(null) and t2:<Department: 0x109600580> (entity: Department; id: 0x10f507590 <x-coredata:///Department/t8F610E34-B04A-4152-A481-75818DEC7DE12> ; data: <fault>)

3.多個context下,使用undo操作時就要慎用驶鹉,有可能會多undo了某些操作绩蜻。比如:

NSUndoManager *undoManager = [NSUndoManager new];
    [[NSManagedObjectContext MR_defaultContext] setUndoManager:undoManager];
    [[[NSManagedObjectContext MR_defaultContext] undoManager] beginUndoGrouping];

當在beginUndoGrouping后,開了一個線程進行了數(shù)據(jù)操作并保存:

dispatch_async(dispatch_get_main_queue(), ^{
        //somthing data operation
        [[NSManagedObjectContext MR_contextForCurrentThread] MR_saveOnlySelfAndWait];
    });

室埋,這時候數(shù)據(jù)會先更新至defaultContext中办绝,再保存至數(shù)據(jù)庫踏兜。如果我們只打算undo在主線程中的更改,調用

    [[[NSManagedObjectContext MR_defaultContext] undoManager] endUndoGrouping];
    [[[NSManagedObjectContext MR_defaultContext] undoManager] undo];

進行回滾八秃,會把線程中執(zhí)行的操作一起回滾掉碱妆。因此這種業(yè)務需求下的undo應該使用另一個MOC進行處理,defaultContext作為最終同時保存的部分昔驱。

    NSUndoManager *undoManager = [NSUndoManager new];
    NSManagedObjectContext *moc = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
    [moc setUndoManager:undoManager];
    [[moc undoManager] beginUndoGrouping];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末疹尾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子骤肛,更是在濱河造成了極大的恐慌纳本,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腋颠,死亡現(xiàn)場離奇詭異繁成,居然都是意外死亡,警方通過查閱死者的電腦和手機淑玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門巾腕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人絮蒿,你說我怎么就攤上這事尊搬。” “怎么了土涝?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵佛寿,是天一觀的道長。 經常有香客問我但壮,道長冀泻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任蜡饵,我火速辦了婚禮弹渔,結果婚禮上,老公的妹妹穿的比我還像新娘验残。我一直安慰自己捞附,他們只是感情好巾乳,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布您没。 她就那樣靜靜地躺著,像睡著了一般胆绊。 火紅的嫁衣襯著肌膚如雪氨鹏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天压状,我揣著相機與錄音仆抵,去河邊找鬼跟继。 笑死,一個胖子當著我的面吹牛镣丑,可吹牛的內容都是我干的舔糖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莺匠,長吁一口氣:“原來是場噩夢啊……” “哼金吗!你這毒婦竟也來了?” 一聲冷哼從身側響起趣竣,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤摇庙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遥缕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卫袒,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年单匣,在試婚紗的時候發(fā)現(xiàn)自己被綠了夕凝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡户秤,死狀恐怖迹冤,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情虎忌,我是刑警寧澤泡徙,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站膜蠢,受9級特大地震影響堪藐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜挑围,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一礁竞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杉辙,春花似錦模捂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至品腹,卻和暖如春岖食,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舞吭。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工泡垃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留析珊,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓蔑穴,卻偏偏與公主長得像忠寻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子存和,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容