为什么 NSFetchedResultsController 没有更新新数据?
我的核心数据模型有两个实体:具有一对多关系的Author
和Book
(一位作者->多本书)。在主视图中,我显示一个书籍列表,其中每个单元格包含书籍名称和作者姓名。该视图还分为多个部分,每个部分的标题是作者姓名。 (请注意,为排序描述符和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.fetchedResultsControllersections]
仍然包含旧作者姓名?请帮忙!
更新 #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: 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
对吧?如果是这样,当作者 NSManagedObject
更改时,NSFetchedResultsController
如何检测 _NSDefaultSectionInfo
对象的更改?
因此这引出了几个问题:
您如何在另一个视图中更改作者的姓名?
更改作者姓名的代码写在上面的saveAuthorName
中。应用程序中的流程如下:
从主
UITableView
中,选择使用导航控制器打开新 Book 视图的书本单元。从书籍视图中,选择选择作者,这将使用导航控制器打开新的选择作者视图。 (所有作者都列在
UITableView
中)从“选择作者”视图中,选择使用导航控制器打开新“编辑作者”视图的任何作者。
在“编辑作者”视图中更改作者姓名并保存,这会关闭视图并将上一个视图(选择作者)带入导航控制器堆栈中。
现在可以选择不同的作者并对其进行编辑等等......直到关闭此视图。 (带来书籍视图)
关闭书籍视图,进入显示所有书籍的主视图。
单元格中的作者姓名是旧的还是只是部分标题?
单元格已使用作者姓名完美更新(感谢 saveAuthorName
中调用的 willChangeValueForKey
和 didChangeValueForKey
)。只有节标题是旧的。
您的委托方法是什么样的?
您能具体说明是哪一个吗?我在上面的代码部分中编写了所有看起来与我相关的委托方法。这包括:
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
的当前配置如下:
- 实体:书籍
- 排序描述符:author.namesectionNameKeyPath
- :author.name
更改书籍名称(而不是作者姓名)将触发 didChangeSection
事件,当 NSFetchedResultsController
配置如下时:
- Entity: Book
- Sort Descriptor: name
- sectionNameKeyPath: name
这意味着委托已正确挂接到NSFetchedResultsController。
当更改作者姓名不足以用于 NSFetchedResultsController< 时,看起来会调用
[book willChangeValueForKey:@"author"]
和 [book didChangeValueForKey:@"author"]
/code> 以监视部分更改。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
通常,如果您正在为进行更改的
NSFetchedResultsController
和UIViewController
处理单个NSManagedObjectContext
,则不需要保存更改。如果您有多个
NSManagedObjectContext
,则这一点不适用。假设您有一个 NSManagedObjectContext ,我会确保您在 NSFetchedResultsController 上设置了委托,并在委托方法中放置断点,以查看是否收到任何回调。
我还将使用调试器并打印出您正在使用的 NSManagedObject 的指针,并确保它们是相同的。如果不是,则表明 NSManagedObjectContext 存在问题。
回复 #1
嗯,还是有点模糊。您是说章节的数量不正确吗?
根据您发布的代码,您只需从
NSFetchedResultsController
检索NSManagedObject
实例。也许对于那是什么有些困惑?NSFetchedResultsController 只是一个具有一个或多个部分的容器。这些部分也只是一个包含一个或多个 NSManagedObject 实例的容器。这些是 相同
NSManagedObject
实例,您可以在应用程序中的其他任何位置访问这些实例(假设单个NSManagedObjectContext
设计)。因此,如果您在应用程序中任何地方更改
NSManagedObject
中的数据,它将在NSFetchedResultsController
中更新,因为它是同一个对象,而不是复制但完全相同的对象。因此,这引出了几个问题:
-[UITableViewDatasource tableView: titleForHeaderInSection:]
在您从编辑返回后会触发吗?响应 #2
您是否正在实施
-controller: didChangeSection: atIndex: forChangeType:
?如果没有,请这样做并告诉我它是否会火。如果着火了,什么时候着火?在调用-[UITableViewDatasource tableView: titleForHeaderInSection:]
之前还是之后?回应 #3
这听起来像是 Apple 的 bug。
一些想法:
Generally you should not need to save the changes if you are dealing with a single
NSManagedObjectContext
for both theNSFetchedResultsController
and theUIViewController
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 theNSFetchedResultsController
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 theNSFetchedResultsController
. 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 ofNSManagedObject
that you would access anywhere else in your application (assuming a singleNSManagedObjectContext
design).Therefore if you change the data in a
NSManagedObject
anywhere in your application it will be updated in theNSFetchedResultsController
because it is the same object, not a copy but the exact same object.So this leads to a couple of questions:
-[UITableViewDatasource tableView: titleForHeaderInSection:]
is firing after you return from the edit?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:
今天有了 Swift,一切都变得更加容易。
NSFetchedResultsControllerDelegate
NSFetchedResultsController
的委托。fetchedResultsController
上使用performFetch
,它会吞掉通常发送到委托的事件。Today with Swift everything is easier.
NSFetchedResultsControllerDelegate
in your controllerNSFetchedResultsController
.performFetch
on yourfetchedResultsController
, it swallows up the event which normally goes to the delegate.为什么不提交保存更改的事务?
Why don't you commit the transactions saving changes?
我知道这是一个非常古老的问题,但在花了这么多时间研究、测试并尝试为自己解决同样的情况之后,我想分享我的发现。我相当确定这是一个 Apple 错误,并且我已针对
NSFetchedResultsSectionInfo
的 name 属性问题提交了反馈 FB7960282,该问题并不总是具有正确的值。我的 Objc 有点生疏,所以我将在 Swift 中给出我的解决方法。
上面的函数假设 self.frc 是您的获取结果控制器。
顺便说一句,由于您的查询返回书籍,如果您想对作者的更改做出反应,您应该观察
NSManagedObjectContextDidSave
通知。I know this is a very old question, but after spending so much time researching, testing and trying to resolve the same situation for myself, I want to share my findings. I am fairly certain that this is an Apple bug and I have filed Feedback FB7960282 for the issue of the name property of the
NSFetchedResultsSectionInfo
not always having the correct value.My Objc is a little rusty, so I will give my the workaround in Swift.
The above function assumes that self.frc is your fetched results controller.
BTW, since your query is returning books, if you want to react to changes to authors, you should observe for
NSManagedObjectContextDidSave
notifications.