提问人:Joshua 提问时间:2/26/2012 最后编辑:CommunityJoshua 更新时间:7/13/2020 访问量:12506
为什么 NSFetchedResultsController 没有使用新数据进行更新?
Why NSFetchedResultsController is not being updated with new data?
问:
我的核心数据模型有两个实体:一个是多对多关系(一个作者>多本书)。在主视图中,我显示一个书籍列表,其中每个单元格都包含书籍名称和作者姓名。该视图还分为多个部分,每个部分的标题是作者姓名。(请注意,为排序描述符和 sectionNameKeyPath 设置了“author.name”)Author
Book
下面是代码(为清楚起见而简化):
- (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
因此,这导致了几个问题:
在另一个视图中,您如何更改作者的姓名?
更改作者姓名的代码写在上面。App中的流程如下:saveAuthorName
从主 中,选择书籍单元格,该单元格使用导航控制器打开新的书籍视图。
UITableView
从书籍视图中,选择“选择作者”,这将使用导航控制器打开新的“选择作者”视图。(所有作者都列在
UITableView
)从“选择-作者”视图中,选择任何作者,这将使用导航控制器打开新的“编辑-作者”视图。
在“编辑-作者”视图中,更改作者姓名并保存,这将关闭视图并将上一个视图 (Select-Author) 引入导航控制器堆栈。
现在可以选择不同的作者并对其进行编辑等等。直到关闭此视图。(带来书籍视图)
关闭书籍视图,进入显示所有书籍的主视图。
单元格中的作者姓名是旧的还是只是部分标题?
Cell 使用作者姓名完美更新(感谢 和 调用)。只有节标题是旧的。willChangeValueForKey
didChangeValueForKey
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
- 实体:Book
- 排序描述符:author.name
- sectionNameKeyPath:author.name
更改书籍名称(而不是作者姓名)将在以下配置时触发事件:didChangeSection
NSFetchedResultsController
- 实体:Book
- 排序描述符:name
- sectionNameKeyPath:名称
这意味着委托已正确挂接到 NSFetchedResultsController。
它看起来像是呼叫,当更改作者姓名不足以监控部分更改时。[book willChangeValueForKey:@"author"]
[book didChangeValueForKey:@"author"]
NSFetchedResultsController
答:
为什么不提交保存更改的事务?
- (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 ..
}
}
评论
通常,如果您同时处理 和 进行更改的单个更改,则不需要保存更改。NSManagedObjectContext
NSFetchedResultsController
UIViewController
如果您有多个 .NSManagedObjectContext
假设你有一个,我会确保你在委托上设置了委托,并在委托方法中放置断点,看看你是否得到了任何回调。NSManagedObjectContext
NSFetchedResultsController
我还将使用调试器并打印出您正在使用的 NSManagedObject 的指针,并确保它们相同。如果不是,则表明 .NSManagedObjectContext
响应 #1
嗯,还是有点模糊。你是说部分数量不正确吗?
根据您发布的代码,您只需从 .也许对那是什么有一些困惑?NSManagedObject
NSFetchedResultsController
只是一个包含一个或多个部分的容器。这些部分也只是一个容器,用于保存一个或多个 NSManagedObject 实例。这些实例与应用程序中其他任何位置访问的实例相同(假设是单个设计)。NSFetchedResultsController
NSManagedObject
NSManagedObjectContext
因此,如果在应用程序中的任意位置更改数据,它将在 中更新,因为它是同一个对象,不是副本,而是完全相同的对象。NSManagedObject
NSFetchedResultsController
因此,这导致了几个问题:
- 在另一个视图中,您如何更改作者的姓名?
- 单元格中的作者姓名是旧的还是只是部分标题?
- 您的委托方法是什么样子的?
- 你确定你的解雇是在你从编辑回来后吗?
-[UITableViewDatasource tableView: titleForHeaderInSection:]
- 返回时开火的方法是什么?
NSFetchedResultsControllerDelegate
响应 #2
你在实施吗?如果没有,请这样做并告诉我它是否触发。如果它确实开火了,什么时候开火?在调用 ? 之前或之后?-controller: didChangeSection: atIndex: forChangeType:
-[UITableViewDatasource tableView: titleForHeaderInSection:]
响应 #3
这听起来像是苹果的错误。
几点想法:
- 如果你在作者被挠痒痒之后执行 -performFetch: 会发生什么?我想知道这是否可行。
- 我强烈建议您为此创建一个测试用例。然后你可以把它提交给苹果公司进行雷达(至关重要),我也可以进行测试,看看是否有干净的解决方案。
评论
如今,有了 Swift,一切都变得简单了。
- 在控制器中实现
NSFetchedResultsControllerDelegate
- 将控制器设置为 .
NSFetchedResultsController
- 不要在 上使用,它会吞噬通常发送给委托的事件。
performFetch
fetchedResultsController
- 更改托管对象 - 应调用>委托方法。
评论
我知道这是一个非常古老的问题,但在花了这么多时间研究、测试并试图为自己解决同样的情况之后,我想分享我的发现。我相当确定这是一个 Apple 错误,我已经提交了反馈FB7960282,以解决 name 属性的问题并不总是具有正确的值。NSFetchedResultsSectionInfo
我的 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
评论