我有一个非常标准的应用程序,它带有一个 UITableViewController,它从 CS193P stanford 类扩展了 CoreDataTableViewController(它只是 UITableViewController 的一个扩展,它实现了 NSFetchedResultsControllerDelegate 以及 NSFetchedResultsControllerDelegate 文档中的所有样板代码)。
无论如何,我的表格显示了一个项目列表。这些项目有一个称为position
整数的属性(Core Data 中的 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 上的谓词过滤和位置上的排序描述符排序。在 fetch 之后从 NSFetchedResultsController 循环 fetchedObjects 时会记录下一行。首先显示项目名称(aaaa、bbbb 等),然后显示 fetchedObjects 数组中的数组位置,然后是 position 属性的值。你可以看到它们是如何排列的。
当我添加一个新项目,然后返回父视图,然后再次转发到列表时,麻烦就来了。新项目被添加到正确的位置(结束)。但是当我来回前进时,有几个项目出了问题。
起初我以为可能没有再次执行提取或 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!由于这实际上是在 fetch 之后立即获取的对象,并且由于 fetchRequestcontroller 的 sortDescriptor 和 predicate 显然是正确的,这怎么可能呢?
起初我认为这可能是表视图中的一个问题,但后来我在 fetchObjects 之后添加了这个调试日志,所以我知道它是 fetch 的结果。
我还考虑到可能 NSNumbers 不能自动排序,所以我添加了自己的比较器来对整数值进行排序。但没有区别。
请注意,如果我再次来回前进,下一次 fetch 会将事情按正确的顺序放回原处。所有后续提取也是如此。加载后只会发生这种情况。
有任何想法吗?
[更新]
在评论中进行了一些有益的讨论之后(感谢@MartinR 和@tc 感兴趣),我稍微简化了一些事情并添加了一些代码来演示正在发生的事情。简化:
- 我现在对项目“标题”进行排序,因为它是一个简单的 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
调用了来自 cs193p 斯坦福课程的 CoreDataTableViewController 超类:
- (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. 将delegate
FRC 设置为此 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 后进行新的提取)另请注意从列表列表中前进时,这不会设置委托或执行提取,因为设置列表属性已经做到了(设置委托并执行提取)。所以事实上,从弹出 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 是。