为什么NSFetchedResultsController没有使用新数据更新? [英] Why NSFetchedResultsController is not being updated with new data?
问题描述
我的核心数据模型有两个实体:作者
和书
>很多书)。在主视图中,我显示一个书籍列表,其中每个单元格包含图书名称和作者名称。视图还分为几个部分,其中每个部分标题是作者名称。 (注意,对于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 {
pre>
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]){
//处理错误
}
}
为什么
[self.fetchedResultsController sections]
仍包含旧的作者名?请帮助!
更新#1
本节涉及Marcus的回应#1 b
$ b
哼,还是有些模糊。您是说部分数量不正确吗?
部分数量未更改。
Sections
属性数组中的对象内容不正确。
你的代码张贴你只是从NSFetchedResultsController检索NSManagedObject实例。
在代码中,我将检索
NSManagedObject
的实例,以便显示每个表格单元的书名和作者姓名(当
cellForRowAtIndexPath $ c $)时,
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 $ c>时,
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:
如果您意味着返回主视图(意味着关闭书视图),则调用以下方法:
-
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
任何地方的数据
NSFetchedResultsController
中更新,因为它是同一个对象,而不是副本,但完全相同的对象。 因此,这会导致一些问题:
- 在这个视图中如何更改作者的姓名? / li>
- 单元格中的作者姓名是旧的还是只是节标题?
- 您确定您的
- [UITableViewDatasource tableView:titleForHeaderInSection:] / code>在您返回之后触发?
-
NSFetchedResultsControllerDelegate
li>
回应#2
您正在执行 :didChangeSection:atIndex:forChangeType:
?如果没有,请这样做,并告诉我,如果它火。如果它发火,什么时候发火?在 - [UITableViewDatasource tableView:titleForHeaderInSection:]
?
的响应之前或之后响应#3
这听起来像一个苹果bug。
有几个想法:
- 如果你在执行一个-performFetch之后发生了什么?我不知道这是否有效。
- 我强烈建议您为此创建一个测试用例。然后,您可以将其提交给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:
- How are you changing the author's name in this other view?
- Is the author name in the cell old or just the section header?
- What do your delegate methods look like?
- Are you certain that your
-[UITableViewDatasource tableView: titleForHeaderInSection:]
is firing after you return from the edit? - 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:
- What happens if you do a -performFetch: after the author is tickled? I wonder if that would work.
- 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屋!