【发布时间】:2012-10-30 03:31:54
【问题描述】:
我有一个非常标准的应用程序,它带有一个扩展 CoreDataTableViewController 的 UITableViewController 来自 CS193P stanford 类(它只是 UITableViewController 实现的扩展 带有所有样板代码的 NSFetchedResultsControllerDelegate 来自 NSFetchedResultsControllerDelegate 文档)。
无论如何,我的表格显示了一个项目列表。这些项目有一个名为position 的属性,它是
整数(核心数据中的 NSNumber)和 NSFetchedResultsController 设置有
NSSortDescriptor 按位置排序。
这通常有效:当我的表打开时,performFetch 已完成并且项目位于右侧 命令。 我添加了一些日志消息进行调试。
fetching Item with pedicate: parentList.guid == "123" and sort: (position, ascending, BLOCK(0x6bf1ca0))
fetched item: aaaaa has array:pos = 0 : 0
fetched item: bbbb has array:pos = 1 : 1
fetched item: cccc has array:pos = 2 : 2
fetched item: dddddd has array:pos = 3 : 3
第一行说的是 performFetch 发生在 GUID 上的谓词过滤 以及按位置排序的排序描述符。 当从 NSFetchedResultsController 循环遍历 fetchedObjects 时会记录下一行 取回之后。首先显示项目名称(aaaa、bbbb 等),然后是数组中的位置 fetchedObjects 数组,然后是 position 属性的值。 您可以看到它们是如何排列的。
当我添加一个新项目,然后回到父视图,然后转发到列表时,麻烦就来了 再次。新项目被添加到正确的位置(结束)。但是当我来回前进时 有几件物品有问题。
起初我以为可能没有再次执行 fetch 或者 sortDescriptor 丢失了 但日志显示提取正在发生,您可以看到事情发生了故障。
fetching Item with pedicate: parentList.guid == "123" and sort: (position, ascending, BLOCK(0x6bf1ca0))
fetched item: bbbb has array:pos = 0 : 1 <= BAD
fetched item: cccc has array:pos = 1 : 2 <= BAD
fetched item: aaaaa has array:pos = 2 : 0 <= BAD
fetched item: dddddd has array:pos = 3 : 3
fetched item: eeee has array:pos = 4 : 4
请看:注意数组项 0 的位置为 1,数组项 2 的位置为 0,而 1 的位置为 2! 由于这实际上是获取后立即获取的对象, 并且由于 fetchRequestcontroller 的 sortDescriptor 和谓词显然是正确的,如何 这甚至可能吗?
起初我认为这可能是表格视图中的问题,但后来我添加了这个调试日志 在 fetchObjects 之后,所以我知道这是 fetch 的结果。
我也考虑到可能 NSNumbers 不能自动排序,所以我添加了自己的比较器 对整数值进行排序。但没有区别。
请注意,如果我再次来回前进,下一次 fetch 会将内容按正确的顺序放回原处。 所有后续提取也是如此。加载后只会发生这种情况。
有什么想法吗?
[更新]
在 cmets 中进行了一些有益的讨论(感谢 @MartinR 和 @tc 的关注)后,我稍微简化了一些事情并添加了一些代码来演示正在发生的事情。 简化:
- 我现在对项目“title”进行排序,因为它是一个简单的 NSString。
- 我不再使用子 NSManagedObjectContext 来创建新项目 - 它们直接在与列表相同的 MOC 中创建并立即(同步)保存
添加一些代码来演示: 基本设置是项目列表的列表。标准待办事项应用程序的东西。所以我的 CoreData 模型包含列表和项目。每个列表都有一组项目(1-many),每个项目都有对其父列表的引用。
UI 是 2 个 TVC:ListOfListsTVC,单击列表名称会转到 ListOfItemsTVC
现在,由于项目列表用于不同的列表,因此每次设置新列表时都会设置一个全新的 FRC。这发生在这里:
- (void)setupFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"item"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"parentList.guid = %@", self.list.guid];
request.predicate = predicate;
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.list.managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
self.debug = YES;
}
self.fetchedResultsController 调用 CoreDataTableViewController 超类,该超类是 cs193p 斯坦福课程的逐字记录:
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
self.debug = YES;
NSFetchedResultsController *oldfrc = _fetchedResultsController;
if (newfrc != oldfrc) {
_fetchedResultsController = newfrc;
newfrc.delegate = self;
if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
self.title = newfrc.fetchRequest.entity.name;
}
if (newfrc) {
if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
[self performFetch];
} else {
if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self.tableView reloadData];
}
}
}
主要是调试信息,但其中的关键语句有: 1. 设置属性; 2. 将 FRC 的 delegate 设置为这个 TVC (self) 和 3. 立即获取。
performFetch 在同一个 CoreDataTableViewController 类中,并转储了我上面列出的所有调试信息:项目的名称、fetchedObjects 数组中的位置和位置的值。 (它实际上也是一个获取列表的通用方法,但我对 Item 类进行了测试以获取额外的调试信息)
这里就不一一列举了,关键是:
NSError *error;
[self.fetchedResultsController performFetch:&error];
// Debug:
NSArray *obs = [self.fetchedResultsController fetchedObjects];
// log all the debug info about the items in the fetched array to prove they're not sorted
// ...
[self.tableView reloadData];
所以基本上,获取并重新加载表。
如果数据中有项目列表,当我第一次启动应用程序时,这似乎有效。在我添加一个新项目之后,它似乎失败了。我在一个名为 NewItemTVC 的单独静态 TVC 中执行此操作,而不是委托,我在块中使用回调来保存项目。但效果是一样的:都是同步的。这是我保存在 ListOfItemsTCV 中的块
newItemTVC.saveCancelBlock2 = ^ (BOOL save, NSDictionary *descriptor) {
if (save) {
NSManagedObjectContext *moc = self.fetchedResultsController.managedObjectContext;
Item *newItem = [Item itemWithDescriptor:itemDescriptor inManagedObjectContext:moc];
// here's where I set the position but ignore this for now because
// I'm sorting on "title" to debug and it has the same problem
NSInteger newPosition = self.list.lastPosition + 1;
newItem.position = [NSNumber numberWithInteger:newPosition];
// and finally add it to the list
[self.list addItemsObject:newItem];
NSError *error = nil;
BOOL saved = [moc save:&error];
if (!saved) {
NSLog(@"Unresolved error saving after adding item to parent %@, %@", error, [error userInfo]);
}
}
现在,在我保存项目后,NewItemTVC 会弹出,ListOfItems 会重新加载,执行提取,有时会有正确的顺序,通常不是。在这种情况下,获取是在 viewWillAppear 中执行的。 (以前没有,但我在调试时也添加了这个。现在 viewWillDisappear 将委托设置为 nil,弹出 NewItemTVC 会导致此代码在将 FRC 的委托设置回 TVC 后进行新的提取) 另请注意,当从列表列表中forward 时,这不会设置委托或执行提取,因为设置列表属性已经这样做了(设置委托并执行提取)。 所以事实上,从弹出 NewItemTVC 并在 viewWillAppear 中执行 fetch 返回是排序出现错误的第一个实例。
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.fetchedResultsController != nil && self.fetchedResultsController.delegate != self) {
self.fetchedResultsController.delegate = self;
[self performFetch];
[self.tableView reloadData];
}
}
它真正出错的地方是当我点击返回查看我的 ListOfListsTVC,然后再次点击列表返回到同一个 ListOfItemsTVC(我有 1 个列表还是十几个列表都没有关系) .我第一次这样做时,这些项目总是乱序。有时我可以重复 4 或 5 次,它们仍然会出现故障,但最终经过多次来回后,它们会恢复正常并保持这种状态。
现在我使用的是项目的“标题”而不是位置,这是(已清理的)调试信息。
[808:fb03] [ListOfItemsTVC performFetch] fetching Item with pedicate: parentList.guid == "DD1E1F25-BFC9-46B9-A637-109C0D6F0D1D" and sort: (title, ascending, compare:)
[808:fb03] fetched item: ccccc in array at index 0
[808:fb03] fetched item: aaaaa in arrat at index 1
[808:fb03] fetched item: bbbbb in array at index 2
几次后退后,它稳定为 aaaa、bbbb、ccccc - 正确的顺序。 在我看来,排序只是坏了,或者 FRC 坏了。
【问题讨论】:
-
您确定您的委托已设置吗?不完全确定我是否在跟踪您的示例运行,但如果没有一组并实施,则 fetch 控制器将不会跟踪更改。
-
请注意,您不能在(基于 SQlite)核心数据获取请求中使用基于块的排序描述符。 - 也许你可以展示你的代码来创建排序描述符、获取请求和获取结果控制器。
-
@BenZotto 代表肯定已经设置好了。为了尝试解决这个问题,我将其更改为取消设置并分别在 viewWillDisappear/viewWillAppear 上重新设置,但这没有区别。请注意,新创建和保存的项目确实出现在列表中的正确位置。问题仅在我返回然后再次前进以重新显示列表时才开始
-
@tc 当用户点击保存时,新项目 TVC 关闭后,保存发生在 TVC 的一个块中。 IE。我有一个静态 TVC,用于创建新项目,该项目使用“保存”按钮以模态方式显示,按下它会调用父 TVC 上的一个块,该块会保存,然后弹出 TVC。标准的东西。值得注意的是,我正在为我的新项目使用第二个临时 MOC。当我保存它时,我保存临时 MOC,然后父 MOC,然后我设置位置并再次保存父 MOC。
-
@MartinR 很奇怪。为什么不允许阻止?碰巧我确实使用了一个块来尝试调试问题 - 但这是为了以防 CoreData 无法自然地对 NSNumber 列进行排序。我很确定它可以。 (每次都有效,但第一次)
标签: ios uitableview nsfetchedresultscontroller