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

时间:2012-02-25 18:26:34

标签: iphone ios4 core-data

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

以下是代码(为简洁起见而简化):

- (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实例。或许对于那是什么感到困惑?

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

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

- (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; 

*/

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

  

因此,这会导致一些问题:

     

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

更改作者姓名的代码在上面saveAuthorName中列出。应用程序中的流程如下:

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

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

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

  • 在编辑 - 作者视图中更改作者姓名并保存,以关闭视图并将上一个视图(选择 - 作者)带入导航控制器堆栈。

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

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

  

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

Cell完全更新了作者姓名(感谢willChangeValueForKeydidChangeValueForKey中调用的saveAuthorName。只有章节标题是旧的。

  

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

你能指出哪一个确切吗?我在上面的代码部分编写了与我相关的所有委托方法。这包括:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

还需要其他方法吗?

  

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

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

  

NSFetchedResultsControllerDelegate方法在返回时触发了什么?

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

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

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

  • controllerDidChangeContent:

如果您的意思是在调用方法后返回主视图(意味着关闭Book视图):

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

感谢您的帮助。谢谢!

更新#2

  

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

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

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

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

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

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

为了监控部分更改,更改作者姓名时[book willChangeValueForKey:@"author"][book didChangeValueForKey:@"author"]的调用不足NSFetchedResultsController

4 个答案:

答案 0 :(得分:5)

通常,如果您正在为正在进行更改的NSManagedObjectContextNSFetchedResultsController处理单个UIViewController,则无需保存更改。

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

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

我还会使用调试器并打印出您正在使用的NSManagedObject的指针,并确保它们是相同的。如果不是,那么它会指向NSManagedObjectContext的问题。

回复#1

嗯,还是有点模糊。您是否说数字的部分不正确?

根据您发布的代码,您只需从NSManagedObject检索NSFetchedResultsController个实例。或许对于那是什么感到困惑?

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

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

因此,这会导致一些问题:

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

    您是否正在实施-controller: didChangeSection: atIndex: forChangeType:?如果没有,请这样做并告诉我它是否会发生火灾。如果它发射,它什么时候开火?在致电-[UITableViewDatasource tableView: titleForHeaderInSection:]之前或之后?

    回应#3

    这开始听起来像苹果虫。

    有几点想法:

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

答案 1 :(得分:1)

今天使用Swift一切都变得更容易。

  1. 您在控制器中实施NSFetchedResultsControllerDelegate
  2. 将您的控制器设置为NSFetchedResultsController
  3. 的代表
  4. 请勿在{{1​​}}上使用performFetch,它会吞并通常发给代表的活动。
  5. 更改托管对象 - >应该调用委托方法。

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

我知道这是一个非常老的问题,但是在花了很多时间研究,测试并尝试为自己解决相同的情况之后,我想分享我的发现。我可以肯定这是一个Apple错误,并且我已经针对NSFetchedResultsSectionInfo的name属性问题(并非总是具有正确的值)提交了反馈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通知。