2011-07-12 26 views
4

我在使用帶有簡單一對一關係的NSFetchedResultsController插入時遇到了一些問題。當我創建一個新的Source對象時,它與Target對象具有一對一的關係,它似乎使用NSFetchedResultsChangeInsert和NSFetchedResultsChangeUpdate類型兩次調用 - [(void)controller:(NSFetchedResultsController *)controller didChangeObject ...]這會導致tableview在更新後立即顯示不準確的數據。NSFetchedResultsController更新和一對一關係問題

我可以用一個基於XCode在基於導航的CoreData應用程序中生成的標準模板項目的簡單示例重新創建此項目。該模板使用timeStamp屬性創建一個Event實體。我想爲這個事件添加一個新的實體「Tag」,它與Entity只是一對一的關係,這個想法是每個事件都有一些來自某個標籤列表的特定Tag。我在覈心數據編輯器中創建了事件到標籤的關係,以及從標籤到事件的反向關係。然後我產生NSManagedObject子類都事件和標籤,這是相當標準:

@interface Event : NSManagedObject { 
@private 
} 
@property (nonatomic, retain) NSDate * timeStamp; 
@property (nonatomic, retain) Tag * tag; 

and 
@interface Tag : NSManagedObject { 
@private 
} 
@property (nonatomic, retain) NSString * tagName; 
@property (nonatomic, retain) NSManagedObject * event; 

我則預先填充的標籤實體與推出一些數據,這樣我們就可以從標籤插入時挑一個新的事件。在AppDelegate中,返回persistentStoreCoordinator之前調用此:

NSManagedObjectContext *context = [self managedObjectContext]; 
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Tag" inManagedObjectContext:context]; 
[fetchRequest setEntity:entity]; 

NSError *error = nil; 
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error]; 
//check if Tags haven't already been created. If not, then create them 
if (fetchedObjects.count == 0) { 
    NSLog(@"create new objects for Tag"); 

    Tag *newManagedObject1 = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:context]; 
    newManagedObject1.tagName = @"Home"; 

    Tag *newManagedObject2 = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:context]; 
    newManagedObject2.tagName = @"Office"; 

    Tag *newManagedObject3 = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:context]; 
    newManagedObject3.tagName = @"Shop"; 
} 

[fetchRequest release]; 

if (![context save:&error]) 
{ 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
    abort(); 
} 

現在,我改變了insertNewObject代碼添加標籤的事件屬性,我們要插入。我隨便挑從fetchedObjects的這個例子中,列表中的第一個:

- (void)insertNewObject 
{ 
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext]; 
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity]; 
    Event *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; 
    // If appropriate, configure the new managed object. 
    // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template. 
    [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"]; 

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
    NSEntityDescription *entityTag = [NSEntityDescription entityForName:@"Tag" inManagedObjectContext:context]; 
    [fetchRequest setEntity:entityTag]; 
    NSError *errorTag = nil; 
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&errorTag]; 
    if (fetchedObjects.count > 0) { 
     Tag *newtag = [fetchedObjects objectAtIndex:0]; 
     newManagedObject.tag = newtag; 
    } 

    // Save the context. 
    NSError *error = nil; 
    if (![context save:&error]) 
    { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); 
    } 
} 

我想現在看到的tableview中反映這些變化,所以我做了的UITableViewCell鍵入UITableViewCellStyleSubtitle,改變configureCell向我展示標記名詳細文本標籤:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath 
{ 
    Event *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath]; 
    cell.textLabel.text = [[managedObject valueForKey:@"timeStamp"] description]; 
    cell.detailTextLabel.text = managedObject.tag.tagName; 
} 

現在一切都已到位。當我打電話insertNewObject,它似乎精細打造的第一行,但第二行是第一個重複的,即使時間戳應該是幾秒鐘間隔: enter image description here

當我滾動屏幕上下它會刷新行,然後用正確的時間顯示正確的結果。當我遍歷代碼時,會出現核心問題:插入一個新行似乎是兩次調用[(NSFetchedResultsController *)controller didChangeObject ...],一次用於插入,一次用於更新。我不知道爲什麼更新被調用。這裏的重頭戲:如果我刪除事件和標記之間的相反關係,插入開始工作就好!只有插入被調用,行不重複,並且工作正常。

那麼,它是什麼導致NSFetchedResultsController委託方法被調用兩次的反向關係?我應該在這種情況下生活嗎?我知道如果沒有指定inverse,XCode會發出警告,這似乎是一個壞主意。我在這裏做錯了什麼?這是一個已知的解決方法的已知問題?

謝謝。

+0

對此有何更新?我發現了一個類似的問題。我得到1個上下文更改通知,並且我提取的結果控制器委託被擊中兩次。 –

回答

0

我有同樣的問題。並有一個解決方案。 在某些情況下,在插入或操作之後直接調用管理對象上下文的-(BOOL)save:時,NSFetchedResultsController會被觸發兩次。在我的情況下,我在NSManagedObject中使用對象 - (void)willSave方法,這會導致NSFetchedResultsController觸發兩次。這似乎是一個錯誤。

不要在保存時操作插入的對象對我來說就是一招!

要延遲上下文保存到以後的運行循環似乎是另一種解決方案,例如:

dispatch_async(dispatch_get_main_queue(), ^{ [context save:nil]; }); 
1

至於didChangeObject被稱爲多次,我發現原因之一,這將不會發生。如果在共享NSManagedObjectContext的控制器中有多個NSFetchedResultsController,則在數據發生更改時會多次調用didChangeObject。我偶然發現了同樣的問題,經過一系列測試後,這是我注意到的行爲。我還沒有測試過,如果NSFetchedResultsControllers不會共享NSManagedObjectContext這種行爲將會發生。不幸的是,didChangeObject不知道哪個NSFetchedResultsController觸發了更新。爲了實現我的目標,我最終在代碼中使用了一個標誌。

希望這會有所幫助!

1

您可以使用[的tableView reloadRowsAtIndexPaths:withRowAnimation:對於NSFetchedResultsChangeUpdate,而不是configureCell方法。

case NSFetchedResultsChangeUpdate: 
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 
    break; 
0

NSFetchedResultsController中的對象必須插入永久objectID。在創建對象並保存到持久性存儲之前,它具有臨時對象ID。保存對象後接收永久對象ID。如果具有臨時對象ID的對象被插入到NSFetchedResultsController中,則在保存對象並將其對象ID改爲永久對象後,NSFetchedResults控制器可以報告有關插入假重複對象的信息。 實例化將在NSFetchedResultsController中獲取的對象之後的解決方案 - 只需在其managedObjectContext上調用獲取PerPrivacyIDsForObjects。