【问题标题】:Why NSFetchedResultsController is not being updated with new data?为什么 NSFetchedResultsController 没有更新新数据?
【发布时间】:2023-03-29 03:40:01
【问题描述】:

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

这是代码(为清楚起见进行了简化):

- (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 实例。也许对那是什么有些困惑?

在代码中,我从NSFetchedResultsController 中检索NSManagedObject 实例,以便显示每个表格单元格的书名和作者姓名(当调用cellForRowAtIndexPath 时)。但是,据我所知,UITableView 中每个部分的标题并不是取自 NSManagedObject,而是取自实现 NSFetchedResultsSectionInfo 协议的 _NSDefaultSectionInfo 对象(当调用 titleForHeaderInSection 时)。

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

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

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

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

日志的结果是 <_nsdefaultsectioninfo:>。 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; 

*/

所以如果我错了请纠正我:_NSDefaultSectionInfo 不是NSManagedObject 对吗?如果是这样,当作者NSManagedObject 更改时,NSFetchedResultsController 如何检测_NSDefaultSectionInfo 对象的更改?

所以这就引出了几个问题:

您如何在其他视图中更改作者姓名?

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

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

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

  • 从 Select-Author 视图中,选择使用导航控制器打开新的 Edit-Author 视图的任何作者。

  • 在 Edit-Author 视图中更改作者姓名并保存关闭视图并将前一个视图(Select-Author)带入导航控制器堆栈。

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

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

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

单元格完美更新为作者姓名(感谢saveAuthorName 中调用的willChangeValueForKeydidChangeValueForKey)。只有节标题是旧的。

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

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

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

还需要其他方法吗?

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

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

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

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

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

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

  • controllerDidChangeContent:

如果您的意思是在返回主视图(意味着关闭书本视图)时调用以下方法:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

感谢您的帮助。谢谢!

更新 #2

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

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

  • 实体:书籍
  • 排序描述符:作者姓名
  • sectionNameKeyPath: author.name

NSFetchedResultsController 配置如下时,更改书名(而不是作者姓名)将触发didChangeSection 事件:

  • 实体:书籍
  • 排序描述符:名称
  • sectionNameKeyPath: 名称

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

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

【问题讨论】:

    标签: iphone ios4 core-data


    【解决方案1】:

    一般来说,如果您正在处理单个NSManagedObjectContext 来处理正在进行更改的NSFetchedResultsControllerUIViewController,则通常不需要保存更改。

    如果您有多个NSManagedObjectContext,则不适用。

    假设您有一个NSManagedObjectContext,我会确保您在NSFetchedResultsController 上设置了委托,并在委托方法中放置断点以查看您是否收到任何回调。

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

    响应 #1

    嗯,还是有点模糊。您是说 number 个部分不正确吗?

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

    NSFetchedResultsController 只是一个包含一个或多个部分的容器。这些部分也只是一个包含一个或多个 NSManagedObject 实例的容器。这些是NSManagedObject相同 实例,您可以在应用程序的任何其他位置访问它们(假设单个NSManagedObjectContext 设计)。

    因此,如果您在应用程序中更改NSManagedObject anywhere 中的数据,它将在NSFetchedResultsController 中更新,因为它是同一个对象,而不是副本,而是完全相同的对象。

    所以这就引出了几个问题:

    1. 如何在其他视图中更改作者姓名?
    2. 单元格中的作者姓名是旧的还是只是部分标题?
    3. 您的委托方法是什么样的?
    4. 您确定您的-[UITableViewDatasource tableView: titleForHeaderInSection:] 在您从编辑返回后会触发吗?
    5. 返回时会触发哪些NSFetchedResultsControllerDelegate 方法?

    响应 #2

    您是否正在实施-controller: didChangeSection: atIndex: forChangeType:?如果没有,请这样做并告诉我它是否着火。如果它着火,它什么时候着火?在拨打-[UITableViewDatasource tableView: titleForHeaderInSection:]之前还是之后?

    响应 #3

    这听起来像是 Apple 的错误。

    一些想法:

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

    【讨论】:

    • 非常感谢您的回复马库斯!首先,我只使用一个 NSManagedObjectContext。我在 FRC 的代表中收到了 didChangeObject 和 controllerDidChangeContent。所有获取的对象(类型为 NSManagedObject)正在正确更新。我遇到的问题不在于获取的对象,而在于 FRC 的 Sections 属性。尽管获取的对象会更新,但此 NSArray 中的对象不会更新。我希望我能更好地解释它.. :)
    • 请参阅原始帖子中的更新 #1。谢谢!!
    • 请参阅原帖中的更新 #2。谢谢!
    • 使用 performFetch 时一切正常!实际上这是我目前解决这个问题的方法......我会尽我所能的雷达。非常感谢您的帮助!
    【解决方案2】:

    现在有了 Swift,一切都变得简单了。

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

    【讨论】:

    • 直到今天,我仍然遇到 OP 描述的完全相同的问题。当我有一个完全由部分组成的表视图时——因此我可以使用右侧的索引标题——当有更新时,永远不会调用部分更新委托方法。
    【解决方案3】:

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

    - (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 ..
        }
    }
    

    【讨论】:

    • 我已经在提交数据库事务了,只是忘记写下来了。原帖已更新。 (感谢您的关注)。
    【解决方案4】:

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

    我的 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 通知。

    【讨论】:

      猜你喜欢
      • 2012-12-20
      • 2017-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多