我有一个非常标准的应用程序,带有扩展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
第一行说的是执行了在GUID上使用谓词过滤的performFetch 以及在位置上排序的排序描述符。 从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! 因为这实际上是获取后立即获取的对象, 并且由于fetchRequestcontroller的sortDescriptor和谓词显然是正确的,如何 这甚至可能吗?
起初我认为它可能是表视图中的一个问题,但后来我添加了这个调试日志记录 在获取对象后,我知道它是获取的结果。
我还认为NSNumbers可能无法自动排序,所以我添加了自己的比较器 对整数值进行排序。但没有区别。
请注意,如果我再次前进和后退,下一次获取将以正确的顺序重新开始。 所有后续的提取也将如此。只有这一个在加载后才会发生。
有什么想法吗?
[UPDATE]
在评论中进行了一些有益的讨论后(感谢@MartinR和@tc感兴趣)我已经简化了一些事情并添加了一些代码来演示发生了什么。 简化:
添加一些代码来演示: 基本设置是项目列表的列表。标准Todo-app的东西。所以我的CoreData模型包含列表和项目。每个列表都有一组项目(1-many),每个项目都有一个返回其父列表的引用。
用户界面是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 Stanford课程中逐字记录的:
- (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(自我)和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 时,这不会设置委托或执行提取,因为设置list属性已经这样做(设置委托并执行提取)。 所以实际上,从弹出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次,而且他们仍然会出现故障,但最终经过多次的后退和后退后,他们会按顺序继续保持这种状态。
现在,我已经使用&#34;标题&#34;(#shenized)调试信息了。我的物品,而不是位置。
[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是。
答案 0 :(得分:0)
我在这里发布了我的“解决方案”,但我暂时不接受这个回答,以防万一我使用NSFetchedResultController导致排序是错的,而不只是一个错误。 (见问题下的评论)
最后,我通过获取所有获取的对象并在我想要的位置查找它来作弊。 这对所有情况都不起作用:对于我来说这很容易,因为我正在对整数进行排序,我知道列表是最新的(它没有在后台填充)而且很小,所以没有大的性能查看所有对象的问题。
具体来说:我替换了cellForRowAtIndexPath中的典型行,如下所示:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
...
用这个
{
...
Item *item = [self _lookupItemAtIndexPath:indexPath];
...
这是在这里实施的
- (Item *)_lookupItemAtIndexPath:(NSIndexPath *)indexPath
{
// Since the FRC is sorted on position, _theoretically_ this next call should just
// return the right item, but the damn sorting doesnt always work (esp not after
// a new addition), so we check if it's the one we expect and look it up if not
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (item.position.integerValue != indexPath.row) {
// the sorting failed! so do a lookup to find the item with the right position
// (Note that its crucial that the positions remain contiguous and up-to-date)
NSArray *items = self.fetchedResultsController.fetchedObjects;
NSUInteger index = [items indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if (((Item *)obj).position.integerValue == indexPath.row) {
// found it - stop looking and return YES
*stop = YES;
return YES;
}
else {
return NO;
}
}];
NSAssert1(index != NSNotFound, @"Failed to find the item with the right position: %d", indexPath.row);
item = (Item *)[items objectAtIndex:index];
// Temporary log for debugging: tells me how often the sort is actually failing
NSLog(@"Warning: Item %@ was out of place, found it at %d instead of %d", item.title, index, indexPath.row);
}
return item;
}
请注意,我首先尝试原始查找并测试其是否正确,因此如果不必要,我不会进行“慢速”查找。大多数时候它 _un_necessary - 在添加内容之后它似乎只是一个问题。 (我可以从那里的NSLog消息中看出来)
同样,对于排序失败,这实际上不是回答 - 只是我的具体案例的解决方法 - 所以我不会将其作为答案(但是)
答案 1 :(得分:0)
我对CS193P示例代码有同样的问题。我的解决方案是保存父上下文(在对象创建/修改之后):
NSError* err;
if ([txt.managedObjectContext save:&err]) {
if ([txt.managedObjectContext.parentContext hasChanges]) {
if ([txt.managedObjectContext.parentContext save:&err]) {
NSLog(@"parent context save ok");
} else {
NSLog(@"can't save parent context; error: %@", err);
}
}
} else {
NSLog(@"can't save text; error: %@", err);
}
如果您使用新的UIDocument
方法而不是旧的核心数据模板,并且手动设置NSManagedObjectContext
,NSPersistentStoreCoordinator
和NSManagedObjectModel
,则会显示“错误”。< / p>
答案 2 :(得分:0)
好的,所以我做了一些工具。我试图在虚拟项目中重现问题而不能。我想这可能意味着我的代码中存在错误,而不是核心数据。它太难以找到了。
因为我知道实际保存更新后问题“消失”,所以我研究了如何强制保存。 Apple文档说您不应该保存NSManagedObjectContext
或其父级,因为“您回避了文档执行的其他重要操作”。但是,这是我发现强制保存的唯一方法(仅保存moc或使用updateChangeCount实际上不会导致SQL立即执行)。我不想赞成zxcat的响应,因为我不知道什么是“回避”,但它似乎有用。
更新:一夜之间我已经进一步思考并决定无论我做什么“错误”,NSFetchedResultsController
中仍然存在错误。这是因为它可能最终处于不合逻辑的状态 - 即。 fetchedObjects
的内容与sections
的内容不符。在所有情况下,fetchedObjects
始终是正确的。在某些情况下,像我和OP一样,sections
反映了当前的持久状态,而不是内存状态。