为什么 NSFetchedResultsController 没有使用新数据进行更新?

Why NSFetchedResultsController is not being updated with new data?

提问人:Joshua 提问时间:2/26/2012 最后编辑:CommunityJoshua 更新时间:7/13/2020 访问量:12506

问:

我的核心数据模型有两个实体:一个是多对多关系(一个作者>多本书)。在主视图中,我显示一个书籍列表,其中每个单元格都包含书籍名称和作者姓名。该视图还分为多个部分,每个部分的标题是作者姓名。(请注意,为排序描述符和 sectionNameKeyPath 设置了“author.name”)AuthorBook

下面是代码(为清楚起见而简化):

- (NSFetchedResultsController *)fetchedResultsController {

    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }
    
    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    
    NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"author.name" ascending:YES] autorelease];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
    
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    NSFetchedResultsController *aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"author.name" cacheName:nil] autorelease];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;
    
    NSError *error = nil;
    [self.fetchedResultsController performFetch:&error];
    
    return __fetchedResultsController;
}    

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

     Book* book = [self.fetchedResultsController objectAtIndexPath:indexPath];
     cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", book.name, book.author.name];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller {

    [self.tableView reloadData];
}

现在,如果用户更改作者姓名,然后返回主视图,则单元格和部分将显示旧的作者姓名。在互联网上搜索后,我发现了以下代码,它修复了单元格中的旧作者姓名问题,但未修复部分标题中的旧作者姓名问题:

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }
     
    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    
    // save changes
    NSError * error ;
    if( ![self.moc save:&error] ) {
        // Handle error
    } 
}

为什么仍然包含旧作者姓名?请帮忙![self.fetchedResultsController sections]

更新 #1

本节与 Marcus 的响应 #1 相关

嗯,还是有点模糊。你是说部分数量不正确吗?

部分的数量没有改变。属性数组中对象的内容不正确。Sections

根据您发布的代码,您只需从 NSFetchedResultsController 中检索 NSManagedObject 实例即可。也许对那是什么有一些困惑?

在代码中,我正在从 中检索实例,以便显示每个表单元格的书籍名称和作者姓名(调用时)。但是,根据我的理解,每个部分的标头不是从中获取的,而是从实现协议的对象中获取的(调用时)。NSManagedObjectNSFetchedResultsControllercellForRowAtIndexPathUITableViewNSManagedObject_NSDefaultSectionInfoNSFetchedResultsSectionInfotitleForHeaderInSection

我通过我为调试编写的以下代码实现了这一点:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    id mySection = [[fetchedBooks sections] objectAtIndex:section];
    NSLog(@"%@", mySection)

    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

日志的结果是<_NSDefaultSectionInfo:0x8462b90>。 Sections 属性的 NSFetchedResultsController 文档显示:

/* Returns an array of objects that implement the NSFetchedResultsSectionInfo protocol.
   It's expected that developers use the returned array when implementing the following methods of the UITableViewDataSource protocol

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; 

*/

因此,如果我错了,请纠正我:这不是对的吗?如果是这样,当作者发生更改时,如何检测对象的更改?_NSDefaultSectionInfoNSManagedObjectNSFetchedResultsController_NSDefaultSectionInfoNSManagedObject

因此,这导致了几个问题:

在另一个视图中,您如何更改作者的姓名?

更改作者姓名的代码写在上面。App中的流程如下:saveAuthorName

  • 从主 中,选择书籍单元格,该单元格使用导航控制器打开新的书籍视图。UITableView

  • 从书籍视图中,选择“选择作者”,这将使用导航控制器打开新的“选择作者”视图。(所有作者都列在UITableView)

  • 从“选择-作者”视图中,选择任何作者,这将使用导航控制器打开新的“编辑-作者”视图。

  • 在“编辑-作者”视图中,更改作者姓名并保存,这将关闭视图并将上一个视图 (Select-Author) 引入导航控制器堆栈。

  • 现在可以选择不同的作者并对其进行编辑等等。直到关闭此视图。(带来书籍视图)

  • 关闭书籍视图,进入显示所有书籍的主视图。

单元格中的作者姓名是旧的还是只是部分标题?

Cell 使用作者姓名完美更新(感谢 和 调用)。只有节标题是旧的。willChangeValueForKeydidChangeValueForKeysaveAuthorName

您的委托方法是什么样子的?

你能具体说明是哪一个吗?我在上面的代码部分中编写了所有看起来与我相关的委托方法。这包括:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

还需要其他方法吗?

您确定 -[UITableViewDatasource tableView: titleForHeaderInSection:] 在您从编辑返回后正在触发吗?

100%确定。 带来旧值,并在保存更改后调用它。(在保存更改后也称为更改,但会带来新值)titleForHeaderInSectioncellForRowAtIndexPath

返回时会触发哪些 NSFetchedResultsControllerDelegate 方法?

如果你的意思是在保存时(意味着,之后被调用)以下方法被调用:saveAuthorName

  • controllerWillChangeContent:(不使用它,仅用于调试信息)

  • controller:didChangeObject:(不使用它,仅用于调试信息)

  • controllerDidChangeContent:

如果你的意思是在返回主视图时(意味着关闭 Book 视图),则调用以下方法:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

感谢您的帮助。谢谢!

更新 #2

您是否正在实现 -controller: didChangeSection: atIndex: forChangeType:?

是的,我有,但在更改作者姓名时没有被解雇。的当前配置如下:NSFetchedResultsController

  • 实体:Book
  • 排序描述符:author.name
  • sectionNameKeyPath:author.name

更改书籍名称(而不是作者姓名)将在以下配置时触发事件:didChangeSectionNSFetchedResultsController

  • 实体:Book
  • 排序描述符:name
  • sectionNameKeyPath:名称

这意味着委托已正确挂接到 NSFetchedResultsController。

它看起来像是呼叫,当更改作者姓名不足以监控部分更改时。[book willChangeValueForKey:@"author"][book didChangeValueForKey:@"author"]NSFetchedResultsController

iPhone iOS4 核心数据

评论


答:

0赞 Gabriel 2/26/2012 #1

为什么不提交保存更改的事务?

- (void)saveAuthorName:(NSString *)newName {
    for (Book* book in author.books) {
        [book willChangeValueForKey:@"author"];
    }

    author.name = newName;

    for (Book* book in author.books) {
        [book didChangeValueForKey:@"author"];
    }
    NSError * error ;
    if( ![self.moc save:&error] ) {
         ..... do something ..
    }
}

评论

0赞 Joshua 2/26/2012
我已经在提交数据库事务了,只是忘了写下来。原始帖子已更新。(感谢您的注意)。
6赞 Marcus S. Zarra 2/28/2012 #2

通常,如果您同时处理 和 进行更改的单个更改,则不需要保存更改。NSManagedObjectContextNSFetchedResultsControllerUIViewController

如果您有多个 .NSManagedObjectContext

假设你有一个,我会确保你在委托上设置了委托,并在委托方法中放置断点,看看你是否得到了任何回调。NSManagedObjectContextNSFetchedResultsController

我还将使用调试器并打印出您正在使用的 NSManagedObject 的指针,并确保它们相同。如果不是,则表明 .NSManagedObjectContext

响应 #1

嗯,还是有点模糊。你是说部分数量不正确吗?

根据您发布的代码,您只需从 .也许对那是什么有一些困惑?NSManagedObjectNSFetchedResultsController

只是一个包含一个或多个部分的容器。这些部分也只是一个容器,用于保存一个或多个 NSManagedObject 实例。这些实例与应用程序中其他任何位置访问的实例相同(假设是单个设计)。NSFetchedResultsControllerNSManagedObjectNSManagedObjectContext

因此,如果在应用程序中的任意位置更改数据,它将在 中更新,因为它是同一个对象,不是副本,而是完全相同的对象。NSManagedObjectNSFetchedResultsController

因此,这导致了几个问题:

  1. 在另一个视图中,您如何更改作者的姓名?
  2. 单元格中的作者姓名是旧的还是只是部分标题?
  3. 您的委托方法是什么样子的?
  4. 你确定你的解雇是在你从编辑回来后吗?-[UITableViewDatasource tableView: titleForHeaderInSection:]
  5. 返回时开火的方法是什么?NSFetchedResultsControllerDelegate

响应 #2

你在实施吗?如果没有,请这样做并告诉我它是否触发。如果它确实开火了,什么时候开火?在调用 ? 之前或之后?-controller: didChangeSection: atIndex: forChangeType:-[UITableViewDatasource tableView: titleForHeaderInSection:]

响应 #3

这听起来像是苹果的错误。

几点想法:

  1. 如果你在作者被挠痒痒之后执行 -performFetch: 会发生什么?我想知道这是否可行。
  2. 强烈建议您为此创建一个测试用例。然后你可以把它提交给苹果公司进行雷达(至关重要),我也可以进行测试,看看是否有干净的解决方案。

评论

0赞 Joshua 2/28/2012
非常感谢您的回复,马库斯!首先,我只使用一个 NSManagedObjectContext。我在 FRC 的委托中收到 didChangeObject 和 controllerDidChangeContent。所有提取的对象(类型为 NSManagedObject)都已正确更新。我遇到的问题不在于获取的对象,而在于 FRC 的 Sections 属性。尽管提取的对象会更新,但此 NSArray 中的对象不会更新。我希望我能更好地解释它......:)
0赞 Joshua 3/1/2012
请参阅原始帖子中的更新 #1。 谢谢!!
0赞 Joshua 3/2/2012
请参阅原始帖子中的更新 #2。 谢谢!
0赞 Joshua 3/11/2012
使用 performFetch 时,一切正常!实际上,这是我目前解决问题的方法......我会尽力而为。非常感谢您的帮助!
1赞 Fabian 4/4/2017 #3

如今,有了 Swift,一切都变得简单了。

  1. 在控制器中实现NSFetchedResultsControllerDelegate
  2. 将控制器设置为 .NSFetchedResultsController
  3. 不要在 上使用,它会吞噬通常发送给委托的事件。performFetchfetchedResultsController
  4. 更改托管对象 - 应调用>委托方法。

评论

1赞 John Rogers 6/21/2019
直到今天,我仍然遇到与OP描述的完全相同的问题。当我有一个完全由部分组成的表视图时(因此我可以使用右侧的索引标题),当有更新时,永远不会调用部分更新委托方法。
0赞 Chuck H 7/13/2020 #4

我知道这是一个非常古老的问题,但在花了这么多时间研究、测试并试图为自己解决同样的情况之后,我想分享我的发现。我相当确定这是一个 Apple 错误,我已经提交了反馈FB7960282,以解决 name 属性的问题并不总是具有正确的值。NSFetchedResultsSectionInfo

我的 Objc 有点生锈,所以我会在 Swift 中提供我的解决方法。

// This is a workaround for an apparent bug where the name property of an NSFetchedResultsSectionInfo
// element sometimes provides an incorrect name for the section.
private func name(for section: NSFetchedResultsSectionInfo) -> String {

    // The objects property of the NSFetchedResultsSectionInfo should always have at least one element
    let object = section.objects?.first as! NSObject
    // The name of the section should be the value of the Key Path of all objects in the array
    let name = object.value(forKeyPath: self.frc.sectionNameKeyPath!) as? String ?? ""
    return name
}

上面的函数假定 self.frc 是你获取的结果控制器。

顺便说一句,由于您的查询返回的是书籍,如果您想对作者的更改做出反应,您应该观察通知。NSManagedObjectContextDidSave