为什么NSFetchedResultsController没有使用新数据更新? [英] Why NSFetchedResultsController is not being updated with new data?

查看:126
本文介绍了为什么NSFetchedResultsController没有使用新数据更新?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

   - (NSFetchedResultsController *)fetchedResultsController {

if(__fetchedResultsController!= nil){
return __fetchedResultsController;
}

NSFetchRequest * fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

NSEntityDescription * entity = [NSEntityDescription entityForName:@BookinManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

NSSortDescriptor * sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@author.nameascending:YES] autorelease];
NSArray * sortDescriptors = [NSArray arrayWithObjects:sortDescriptor,nil];

[fetchRequest setSortDescriptors:sortDescriptors];

NSFetchedResultsController * aFetchedResultsController = [[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@author.namecacheName: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];
}

//保存更改
NSError * error;
if(![self.moc save:& error]){
//处理错误
}
}
pre>

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



更新#1



本节涉及Marcus的回应#1 b
$ b


哼,还是有些模糊。您是说部分数量不正确吗?


部分数量未更改。 Sections 属性数组中的对象内容不正确。


你的代码张贴你只是从NSFetchedResultsController检索NSManagedObject实例。


在代码中,我将检索 NSManagedObject 实例,以便显示每个表格单元的书名和作者姓名(当 cellForRowAtIndexPath NSFetchedResultsController c>)。但是, 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文档显示:

  / *返回实现NSFetchedResultsSectionInfo协议的对象数组。 
预计开发人员在实现UITableViewDataSource协议的以下方法时使用返回的数组

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

* /

因此,如果我错了,请更正我: _NSDefaultSectionInfo 不是 NSManagedObject 对吗?如果是这样,当 NSManagedObject 时, NSFetchedResultsController 如何检测 _NSDefaultSectionInfo $ c>更改了?


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



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


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




  • UITableView ,选择使用导航控制器打开新书查看的书单。


  • 在书视图中,选择选择作者,控制器。 (所有作者列在 UITableView



  • 在编辑作者视图中更改作者姓名并保存关闭视图并显示上一个视图(Select-


  • 现在可以选择不同的作者并编辑它,直到关闭此视图。 (提供书本视图)


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





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


Cell完全更新为作者姓名(感谢 willChangeValueForKey code> didChangeValueForKey saveAuthorName 中调用)。只有栏目标题是旧的。


您的委托方法是什么?


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




  • cellForRowAtIndexPath


  • titleForHeaderInSection


  • controllerDidChangeContent




需要任何其他方法吗?


$ b b


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


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



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




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


  • controller:didChangeObject:


  • controllerDidChangeContent:

    b $ b


如果您意味着返回主视图(意味着关闭书视图),则调用以下方法:




  • cellForRowAtIndexPath


  • titleForHeaderInSection


  • numberOfSectionsInTableView


  • numberOfRowsInSection




我感谢您的帮助。



更新#2




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


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




  • 实体: li>
  • 排序描述符:author.name

  • sectionNameKeyPath:author.name


$ b b

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




  • 实体:书

  • 排序描述符:name

  • sectionNameKeyPath:name



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



看起来像调用 [book willChangeValueForKey:@author] [book didChangeValueForKey:@author]

解决方法时, NSFetchedResultsController 方案

一般来说,如果您为 NSFetchedResultsController 处理单个 NSManagedObjectContext code>和正在进行更改的 UIViewController



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



假设你有一个 NSManagedObjectContext ,我会确保你的代理设置在 NSFetchedResultsController



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



响应#1



哼,还是有些模糊。



根据您发布的代码,您只需检索 NSManagedObject 实例从 NSFetchedResultsController



NSFetchedResultsController 只是一个容器,它有一个或多个部分。这些节也只是一个容器,它包含一个或多个NSManagedObject实例。这些是 NSManagedObject 实例,您将在应用程序中的任何其他位置访问(假设一个 NSManagedObjectContext NSManagedObject 任何地方的数据

strong>在您的应用程序中,它将在 NSFetchedResultsController 中更新,因为它是同一个对象,而不是副本,但完全相同的对象。



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


  1. 在这个视图中如何更改作者的姓名? / li>
  2. 单元格中的作者姓名是旧的还是只是节标题?
  3. 您确定您的 - [UITableViewDatasource tableView:titleForHeaderInSection:] 在您返回之后触发?

  4. NSFetchedResultsControllerDelegate li>



回应#2



您正在执行 :didChangeSection:atIndex:forChangeType:?如果没有,请这样做,并告诉我,如果它火。如果它发火,什么时候发火?在 - [UITableViewDatasource tableView:titleForHeaderInSection:]



的响应之前或之后响应#3



这听起来像一个苹果bug。



有几个想法:


  1. 如果你在执行一个-performFetch之后发生了什么?我不知道这是否有效。

  2. 我强烈建议您为此创建一个测试用例。然后,您可以将其提交给Apple进行雷达(重要),我也可以进行测试,看看是否有一个干净的解决方案。


My Core Data model has two entities: Author and Book with a To-Many relationship (one author->many books). In the main view I display a list of books where each cell contains book name and author name. The view is also divided into sections where each section title is the author name. (note that "author.name" is set for both sort descriptor and sectionNameKeyPath)

Here is the code (simplified for clarity):

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

Now, if the user changes the author name and then goes back to main view, the cells and sections will display the old author name. After searching the Internet, I found the following code which fixes the old author name issue in the cells but not in section titles:

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

Why is [self.fetchedResultsController sections] still contains old author names? Please help!

Update #1

This section relates to Response #1 of Marcus

Hmmm, still a little fuzzy. Are you saying the number of sections is incorrect?

The number of sections was not changed. The content of the objects in the Sections property array is incorrect.

Based on your code posted you are simply retrieving the NSManagedObject instances from the NSFetchedResultsController. Perhaps there is some confusion as to what that is?

In the code, I am retrieving the NSManagedObject instances from the NSFetchedResultsController in order to display book name and author name for each table cell (when cellForRowAtIndexPath is called). However, the headers of each section in UITableView are not taken from NSManagedObject to my understanding but are taken from _NSDefaultSectionInfo object which implements NSFetchedResultsSectionInfo protocol (when titleForHeaderInSection is called).

I realized this by the following code I wrote for debugging:

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

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

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

The result of the log was <_NSDefaultSectionInfo: 0x8462b90>. NSFetchedResultsController documentation for Sections property shows:

/* 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; 

*/

So please correct me if I am wrong: _NSDefaultSectionInfo is not an NSManagedObject right? If so, how can NSFetchedResultsController detect changes for _NSDefaultSectionInfo objects when Author NSManagedObject are changed?

So this leads to a couple of questions:

How are you changing the author's name in this other view?

The code for changing the author's name is written above in saveAuthorName. The flow in the App is as follows:

  • From the main UITableView, selecting the book cell which opens new Book view using navigation controller.

  • From the book view, choosing select author which opens new Select-Author view using navigation controller. (all authors are listed in a UITableView)

  • From Select-Author view, selecting any author which opens new Edit-Author view using navigation controller.

  • In the Edit-Author view changing author name and saving which closes view and bring the previous view (Select-Author) in navigation controller stack.

  • Now it's possible to select different author and editing it and so on.. until closing this view. (Brings Book view)

  • Closing Book view, brings to main view where all books are displayed.

Is the author name in the cell old or just the section header?

Cell is perfectly updated with author name (thanks to the willChangeValueForKey and didChangeValueForKey called in saveAuthorName). Only section header is old.

What do your delegate methods look like?

could you please specify which one exactly? I wrote all delegate methods that looks relevant to me in the above code section. This includes:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • controllerDidChangeContent

Any other method is required?

Are you certain that your -[UITableViewDatasource tableView: titleForHeaderInSection:] is firing after you return from the edit?

100% percent sure. titleForHeaderInSection brings old values and it is being called after changes were saved. (cellForRowAtIndexPath is also called after changes were saved but is bringing new values)

What NSFetchedResultsControllerDelegate methods are firing upon the return?

If you mean upon saving (means, after saveAuthorName is called) following methods being called:

  • controllerWillChangeContent: (not using it, just for debug info)

  • controller:didChangeObject: (not using it, just for debug info)

  • controllerDidChangeContent:

If you mean upon returning to main view (means, closing the Book view) following methods being called:

  • cellForRowAtIndexPath

  • titleForHeaderInSection

  • numberOfSectionsInTableView

  • numberOfRowsInSection

I appreciate your help. Thanks!

Update #2

Are you implementing -controller: didChangeSection: atIndex: forChangeType:?

Yes I do, but is does not being fired when changing the author name. The current configuration for the NSFetchedResultsController is as follows:

  • Entity: Book
  • Sort Descriptor: author.name
  • sectionNameKeyPath: author.name

Changing a book name (rather than author name) will fire didChangeSection event when NSFetchedResultsController is configured as follows:

  • Entity: Book
  • Sort Descriptor: name
  • sectionNameKeyPath: name

Which means that the delegate is properly hooked to the NSFetchedResultsController.

It looks as calling [book willChangeValueForKey:@"author"] and [book didChangeValueForKey:@"author"] when changing author name is not enough for NSFetchedResultsController in order to monitor section changes.

解决方案

Generally you should not need to save the changes if you are dealing with a single NSManagedObjectContext for both the NSFetchedResultsController and the UIViewController that is making the changes.

That does not apply if you have more than one NSManagedObjectContext.

Assuming you have one NSManagedObjectContext, I would make sure you have the delegate set on the NSFetchedResultsController and put break points in the delegate methods to see if you are getting any callbacks at all.

I would also use the debugger and print out the pointers for the NSManagedObject(s) you are working with and make sure they are the same. If they are not then it would point to an issue with the NSManagedObjectContext.

Response #1

Hmmm, still a little fuzzy. Are you saying the number of sections is incorrect?

Based on your code posted you are simply retrieving the NSManagedObject instances from the NSFetchedResultsController. Perhaps there is some confusion as to what that is?

The NSFetchedResultsController is merely a container that has one or more sections. Those sections are also just a container that holds one or more NSManagedObject instances. Those are the same instances of NSManagedObject that you would access anywhere else in your application (assuming a single NSManagedObjectContext design).

Therefore if you change the data in a NSManagedObject anywhere in your application it will be updated in the NSFetchedResultsController because it is the same object, not a copy but the exact same object.

So this leads to a couple of questions:

  1. How are you changing the author's name in this other view?
  2. Is the author name in the cell old or just the section header? 
  3. What do your delegate methods look like?
  4. Are you certain that your -[UITableViewDatasource tableView: titleForHeaderInSection:] is firing after you return from the edit?
  5. What NSFetchedResultsControllerDelegate methods are firing upon the return?

Response #2

Are you implementing -controller: didChangeSection: atIndex: forChangeType:? If not, please do so and tell me if it fires. If it does fire, when does it fire? Before or after the call to -[UITableViewDatasource tableView: titleForHeaderInSection:]?

Response #3

This is starting to sound like an Apple bug.

A couple of thoughts:

  1. What happens if you do a -performFetch: after the author is tickled? I wonder if that would work.
  2. I strongly suggest that you create a test case for this. You can then submit it to Apple for a radar (vital) and I can play with the test also and see if there is a clean solution.

这篇关于为什么NSFetchedResultsController没有使用新数据更新?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆