2011-08-19 19 views
9

我有一個非常奇怪的問題,在Core Data中存在反向關係,並且我設法將我的問題減少到最小范例,從基於窗口模板的xcode中的新項目開始與核心數據的支持(即,那裏很少)。未設置反向關係(在KVO處理程序中)

假設我們有一個包含三個實體的核心數據模型:Department,Employee和DepartmentSummary(表示關於部門的某些統計信息的某種實體)。爲了簡單起見,我們只有一對一的關係:

DepartmentSummary Department  Employee 
--------------------------------------------------------- 
        employee <----> department 
department <----> summary 

這就是模型中的全部內容。在application:didFinishLaunchingWithOptions:我們創建了一個員工和部門,成立了志願:

NSManagedObject* employee = 
[NSEntityDescription 
    insertNewObjectForEntityForName:@"Employee" 
    inManagedObjectContext:[self managedObjectContext]]; 
[employee addObserver:self forKeyPath:@"department" options:0 context:nil]; 

NSManagedObject* department = 
    [NSEntityDescription 
    insertNewObjectForEntityForName:@"Department" 
    inManagedObjectContext:[self managedObjectContext]]; 
[department setValue:employee forKey:@"employee"]; 

的國際志願者組織處理程序的目的是爲了儘快僱員的部門設置創建爲部門總結:

- (void) observeValueForKeyPath:(NSString *)keyPath 
         ofObject:(id)object 
         change:(NSDictionary *)change 
         context:(void *)context 
{ 
    [self createSummary:object]; 
} 

createSummary很簡單:它創建一個新的彙總對象並將其與部門相關聯,然後檢查從部門到彙總對象的相反關係也設置爲

- (void) createSummary:(NSManagedObject*)employee 
{ 
    NSManagedObject* department = [employee valueForKey:@"department"]; 
    NSManagedObject* summary = 
    [NSEntityDescription 
     insertNewObjectForEntityForName:@"DepartmentSummary" 
     inManagedObjectContext:[self managedObjectContext]]; 

    [summary setValue:department forKey:@"department"]; 

    NSAssert([department valueForKey:@"summary"] == summary, 
      @"Inverse relation not set"); 
} 

該斷言失敗。事實上,如果我們打印部門和總結對象彙總的部門已設置後,我們得到

entity: DepartmentSummary; 
    id: ..DepartmentSummary/..AA14> ; 
    data: { 
    department = "..Department/..AA13>"; 
    } 

的總結,符合市場預期,但

entity: Department; 
    id: ..Department/..AA13> ; 
    data: { 
    employee = "..Employee/..AA12>"; 
    summary = nil; 
    } 

的部門(用nil概要)。然而,如果我們拖延調用createSummary所以它不運行,直到runloop的下一次迭代:

- (void) observeValueForKeyPath:(NSString *)keyPath 
         ofObject:(id)object 
         change:(NSDictionary *)change 
         context:(void *)context 
{ 
    [self performSelector:@selector(createSummary:) 
       withObject:object 
       afterDelay:0]; 
} 

則一切正常。

延緩斷言而不是做幫助:逆關係真的沒有得到設置對象圖,儘管它在數據庫中獲取設置(如果你要保存數據庫,並重新啓動該應用程序,現在突然出現相反的關係)。

這是Core Data中的一個錯誤嗎?這是我所錯過的記錄行爲嗎?我是否以非預期的方式使用核心數據?

注意的是,國際志願者組織處理程序被調用而核心數據是(自動)設置的(其他)逆:我們手動設置部門的employee領域,核心數據自動設置員工的department場,而這又觸發KVO處理程序。也許這對於Core Data來說太過分了:)實際上,當我們設置

[employee setValue:department forKey:@"department"]; 

而是,所有事情再次按預期工作。

任何指針,將不勝感激。

+0

如果您立即設置摘要,但延遲*斷言*直到下一個runloop? – jtbandes

+0

優秀的問題。我將編輯這個問題來回答它 - 基本上,推遲斷言並沒有幫助。 – edsko

+0

嗨,我注意到相同的,它之前工作... – RolandasR

回答

4

這是一個經典的核心數據問題。該文檔特別聲明:

由於核心數據負責爲您維護對象圖一致性維護,因此只需更改關係的一端,併爲您管理所有其他方面。

但是,在實踐中,這是一個禿頭的謊言,因爲它是不可靠的。

我的問題的答案是這樣的:

這是核心數據中的錯誤?

是的。

這是我記錄的行爲嗎?

NO。

我是否以非預期的方式使用Core Data?

NO。

您已經爲您的問題提供了「正確」的解決方案,我每次使用同一個解決方案更改我製作的每個Core Data應用程序中的關係值。對於數百個案例,推薦模式爲:

[department setValue:employee forKey:@"employee"]; 
[employee setValue:department forKey:@"department"]; 

即,每當您更改關係時自行設置關係反轉。

有人可能會對這個主題有更多的瞭解,或者有更多的正則表單來解決您的問題,但根據我的經驗,沒有辦法保證除非手動建立關係,否則無法保證關係活躍可用(如問題所示)。更重要的是,該解決方案還有其他兩個好處:

  1. 它可以100%的工作。
  2. 它使代碼更具可讀性。

最後一點是違反直覺的。一方面,它似乎使代碼變得複雜,並且通過添加一行代碼來增加代碼的長度,根據文檔,這是一個簡短的單行調用。但根據我的經驗,它所做的就是節省程序員到核心數據編輯器的旅程,從視覺上尋找並確認模型關係,這在時間上更具價值。最好是清楚明確地與擁有改變關係時應該發生的事情的心理模型。

我也建議增加一個簡單的類別NSManagedObject:

@interface NSManagedObject (inverse) 

- (void)setValue:(id)value forKey:(NSString *)key inverseKey:(NSString *)inverse; 

@end 

@implementation NSManagedObject (inverse) 

- (void)setValue:(id)value forKey:(NSString *)key inverseKey:(NSString *)inverse { 
    [self setValue:value forKey:key]; 
    [value setValue:self forKey:inverse]; 
} 

@end 

爲:

[department setValue:employee forKey:@"employee" inverse:@"department"]; 

也有少數情況下,在該類別擴大,但是我會處理,例如,完全以不同的方式刪除。

簡而言之:每次都明確處理所有你自己的關係。核心數據在這方面不值得信賴。

+0

嗯。有趣。我發現它不僅僅是在設定關係時。刪除記錄時會發生類似的問題;反比關係可能不會被設置爲零。你也手動處理?我會擔心我會開始失蹤。現在,我已經確定我從不改變KVO處理程序中的對象圖;我計算需要完成的工作,然後計劃下一次runloop迭代。這是有效的,但我必須承認,這種亂序執行使得代碼更復雜。 – edsko

+0

是的,刪除時,我總是手動設置關係爲零。根據我的經驗,核心數據可以根據文檔中規定的規則可靠地安排要刪除的對象,所以這不是問題。爲了澄清,核心數據實際上做了它所陳述的一切,只是它在「方便」而不是「現在」時執行某些操作,就像您在代碼中發現的那樣。如果你需要一個可用的關係,你應該自己設置。如果下一次runloop設置您的UI時只需要它,讓自動化系統處理它。 – SG1

+0

另外,如果我是你,我會回到改變KVO處理程序中的關係。這是完全正常的。我不會去發明自己的Core Data變更傳播的運行時。這裏真正的洞察力是理解您需要手動管理關係,而不是您需要「等待」並稍後處理更改。 – SG1

0

如何在插入後立即保存ManagedObjectContext?

+0

既不保存MOC也不調用processPendingChanges:有幫助。 – edsko