2013-04-14 33 views
14

假設我們在覈心數據模型中有兩個實體:部門和員工。
該部門與員工有一對多的關係。核心數據 - 父上下文的中斷保留週期

我有以下ManagedObjectContexts:
- 根:連接到持久性存儲協調員
- 主營:與父母根上下文

當我想創建一個員工我做到以下幾點:
- 我有部門在主要方面
- 我創造的主要方面
一個Employee - 我分配部門到員工的部門屬性
- 我省主要方面
- 我保存根環境

這會在Main上下文和Root上下文中創建一個保留週期。

如果我沒有兒童上下文(全部在Root上下文中),那麼我可以通過在Employee上調用refreshObject:mergeChanges來打破保留週期。在我處理兩種情況的情況下,我仍然可以使用該方法來打破主環境中的循環,但是我將如何打破Root環境的循環?

注意:這是一個簡單的例子來描述我的問題。在儀器中,我可以清楚地看到分配數量的增長。在我的應用程序中,我的上下文比一個層次更深,導致了更大的問題,因爲我得到了一個新的實體分配,並保留了每個上下文的保留週期。

更新15/04:NSPrivateQueueConcurrencyType VS NSMainQueueConcurrencyType
既節省了環境後,我可以在同處對象的主要方面進行refreshObject:mergeChanges。正如預期的那樣,這將重新違反Department對象,打破保留週期並在該上下文中釋放Department和Employee實體。

下一步是打破Root上下文中存在的保留週期(保存Main上下文已將實體傳播到Root上下文)。我可以在這裏執行相同的技巧,並使用Department對象的Root上下文中的refreshObject:mergeChanges

奇怪的是:當我的Root上下文是用NSMainQueueConcurrencyType創建的(所有的分配都是重新發生故障和處置),但是在我的Root上下文創建時使用NSPrivateQueueConcurrencyType(所有分配都重新發生故障,但不是 dealloced)。

邊注:爲根上下文中的所有操作都在一個performBlock(與等待)進行調用

更新15/04:第2部分
當我做另外一個(沒用,因爲沒有更改)使用NSPrivateQueueConcurrencyType保存或回滾Root上下文時,對象似乎被解除分配。我不明白爲什麼這不像NSMainQueueConcurrencyType一樣。

更新16/04:演示項目
我已經創建了一個演示項目:http://codegazer.com/code/CoreDataTest.zip

更新21/04:四處
謝謝喬迪Hagings您的幫助!
我正在嘗試將refreshObject:mergeChanges移出我的ManagedObject didSave方法。

你能向我解釋的區別:

[rootContext performBlock:^{ 
    [rootContext save:nil]; 
    for (NSManagedObject *mo in rootContext.registeredObjects) 
     [rootContext refreshObject:mo mergeChanges:NO]; 
}]; 

[rootContext performBlock:^{ 
    [rootContext save:nil]; 
    [rootContext performBlock:^{ 
     for (NSManagedObject *mo in rootContext.registeredObjects) 
      [rootContext refreshObject:mo mergeChanges:NO]; 
    }]; 
}]; 

頂部一個不釋放對象,下一個呢。

+0

有趣的問題。當您將其分配給員工的部門屬性時,部門實體在哪個上下文中? –

+0

該部門在主要背景下 – Zyphrax

+0

你有一個小的代碼測試用例來證明這一點嗎?你如何保存根上下文?另外,當你轉儲registeredObjects時你看到了什麼?請記住,'performBlock'包裝了一個完整的「用戶事件」,但'performBlockAndWait'不包含。 –

回答

10

我看着你的樣本項目。榮譽張貼。

首先,你所看到的行爲是不是一個錯誤......至少在覈心數據。如您所知,關係會導致保留週期,必須手動打破(文檔記錄在此:https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html)。

你的代碼是在didSave:這樣做。可能有更好的地方來打破這個循環,但這是另一回事。

請注意,您可以通過查看registeredObjects屬性輕鬆查看MOC中註冊了哪些對象。

但是,您的示例將永遠不會釋放根上下文中的引用,因爲從未在該MOC上調用processPendingEvents。因此,MOC中的註冊對象將永遠不會被釋放。

核心數據有一個叫做概念「用戶事件」。默認情況下,「用戶事件」被正確包裝在主運行循環中。

然而,不是在主線程MOCS,你是負責確保用戶事件的正確處理。看到這個文檔:http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html,特別是標題Track Changes in Other Threads Using Notifications段的最後一段。

當你給performBlock打電話時,你給它的塊被封裝在一個完整的用戶事件中。但是,performBlockAndWait並非如此。因此,私人上下文MOC將保持這些對象在registeredObjects集合中,直到調用processPendingChanges

在您的示例中,如果您在performBlockAndWait內調用processPendingChanges或將其更改爲performBlock,則可以看到發佈的對象。其中任何一個都將確保MOC完成當前用戶事件並從registeredObjects集合中刪除對象。

編輯

在回答您的編輯......這不是第一個不dealloc的對象。這就是MOC仍然有被註冊爲故障的對象。發生保存,在同一事件。如果您只發出一個無操作塊[context performBlock:^{}],您將看到從MOC中刪除的對象。

因此,您不必擔心它,因爲在該MOC的下一個操作中,對象將被清除。你不應該有一個長期運行的背景MOC,無論如何不會做任何事情,所以這對你來說應該不是什麼大不了的事情。

一般而言,您不想只刷新所有對象。但是,如果您希望在保存後刪除所有對象,那麼您在didSave:中的原始概念是合理的,就像在保存過程中發生的那樣。但是,這將會在所有情況下都會將對象錯誤(您可能不想要)。你可能只想要這種背景MOC的惡魔般的方法。您可以在didSave:中檢查object.managedObjectContext,但這不是一個好主意。更好的辦法是安裝的處理程序DidSave通知...

id observer = [[NSNotificationCenter defaultCenter] 
    addObserverForName:NSManagedObjectContextDidSaveNotification 
       object:rootContext 
       queue:nil 
      usingBlock:^(NSNotification *note) { 
    for (NSManagedObject *mo in rootContext.registeredObjects) { 
     [rootContext refreshObject:mo mergeChanges:NO]; 
    } 
}]; 

你會看到,這可能給你想要的東西......雖然只有你能確定你真正想完成。

+0

太好了,非常感謝你的解釋!我知道我不得不用'''refreshObject:mergeChanges''來打破保留週期,但我不知道用戶事件。我在開幕式上添加了一個關於打破保留週期的小問題,你能向我解釋一下這個區別嗎?這也是由用戶事件造成的嗎? – Zyphrax

+0

再次感謝!我將在我的應用中探索通知方法。如果你決定寫一本關於核心數據的書,請讓我知道:) – Zyphrax

+0

Jody,我遇到了一個可能的核心數據錯誤。我認爲你是核心數據的主人:),你可能看看:http://stackoverflow.com/questions/31805678/core-data-nsobjectid-and-nstemporaryobjectid-leaks – Zyphrax

1

當保存到根上下文中時,唯一擁有強對象的對象是根上下文本身,因此,如果你重置它,那麼對象將在根上下文中被釋放。
您節省流量應爲:
-save主要
-save根
-reset根

我沒有重置或刷新主上下文中的對象,即使沒有泄漏或殭屍是找到。內存似乎在父上下文的保存和重置之後被分配和釋放。

+0

使用-reset是一種我不能使用的大錘方法。它將使所有居住在我的根環境中的實體無效(不僅僅是重新排錯它們,而且使它們完全無法使用)。這會使整個context-parentcontext系統無用。 – Zyphrax

+0

如果您的對象生活在您的父上下文中,則對它們的所有更改都將與您從子上下文中引入的更改一起保存爲allog。在這種情況下,您將不得不保存父上下文,並將對象保留在那裏(您可能需要手動刷新它們)。在任何情況下,一旦上下文被重置或釋放,它將拒絕所有已註冊的對象並將它們轉換爲錯誤==>中斷保留週期。您可能需要考慮單獨環境中的更改,並稍後使用通知進行合併。 –

+0

我的根上下文用於顯示屏幕上的項目,由KVO觀察,有相當多的活動ManagedObjects。當我重置根上下文時,所有這些對象都變得不可用。我不得不重新初始化我的應用程序的整個部分,以讓他們回來。我簡直無法想象這是母公司MOC兒童MOC應該如何工作的原因。我不明白爲什麼沒有更多的人遇到這個問題。基本上關係+父母/孩子MOC =記憶問題。 – Zyphrax

3

上述步驟是您在覈心數據中執行的常見任務。蘋果在Core Data Programming Guide: Object Lifetime Management中明確記載了副作用。

當你有管理對象之間的關係,每個對象 保持着強勁的參考對象或對象與它相關 。這會導致強烈的參考週期。爲確保 參考週期被破壞,當完成對象時,您可以使用託管對象上下文方法refreshObject:mergeChanges: 將其變爲故障。

只有當對象不是故障時,對象纔會保持強相互引用,但活動實例爲NSManagedObject。使用嵌套的上下文,將對象保存在主上下文中,那麼應將應該的更改傳播到您的根上下文。但是,除非您在根環境中獲取它們,否則不應創建保留週期。完成保存主環境後,刷新這些對象應該是所需要的。

關於內存佔用一般:

如果你覺得分配已生長出來的手,你可以嘗試讓你執行任務的組織你的代碼,這導致點火故障,以大量的對象,在您完成任務時丟棄的單獨上下文中。

此外,如果您使用的撤消管理器,

與上下文相關的撤消管理器保持強勁引用 任何改變管理對象。默認情況下,在OS X中,上下文的撤銷 管理器保留無限制的撤銷/重做堆棧。要限制您的應用程序的內存佔用量,應該確保在適當的時候使用 (使用removeAllActions)上下文的撤消堆棧和 。除非您保持對上下文的撤消管理器的強烈引用,否則會將其與上下文解除分配。

更新#1:

與分配儀器和片的書面尤其來測試該代碼試驗後,我們可以確認根上下文不釋放存儲器。要麼這是一個框架錯誤,要麼是設計意圖。我發現一篇文章here描述了同樣的問題。

調用[context reset][context save:]確實釋放了內存,因爲它應該。另外我注意到,在保存之前,根上下文有我通過子集上下文在[context insertedObjects]集合中插入的所有對象。迭代他們並做[context refreshObject:mergeChanges:NO]確實重新錯誤的對象。

因此,似乎沒有什麼變通方法,但是否這是一個錯誤,並且會在某個即將發佈的版本中得到修復,或者它會保持原樣設計,我不知道。

+0

在傳播到Root上下文之後,保留週期也將存在於Root上下文中。我已經用儀器測試過了。我更新了更多信息的問題。我沒有使用撤消管理器(iOS默認)。 – Zyphrax

+0

夠公平的,稍後我會有更多時間做自己的實驗。我會告訴你關於我的發現。 – svena

+0

非常感謝! – Zyphrax