NSFetchedResultsController忽略fetchLimit?

我有一个NSFetchedResultsController来更新与核心数据内容的UITableView。 这是非常标准的东西,我相信你们都看过很多次,但是我遇到了一些小问题。 首先这是我的代码:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Article" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setFetchLimit:20]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(folder.hidden == NO)"]; [fetchRequest setPredicate:predicate]; NSSortDescriptor *sort1 = [NSSortDescriptor sortDescriptorWithKey:@"sortDate" ascending:NO]; [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort1, nil]]; NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; [fetchRequest release]; controller.delegate = self; self.fetchedResultsController = controller; [controller release]; NSError *error = nil; [self.fetchedResultsController performFetch:&error]; if (error) { // TODO send error notification NSLog(@"%@", [error localizedDescription]); } 

问题是,最初商店没有任何实体,因为它从web服务下载和同步。 会发生什么是NSFetchedResultsController从商店中的超过150行的实体填充表,这是多lessWeb服务返回。 但是我设置了一个20的取值限制,似乎忽略了这个限制。 但是,如果我closures了应用程序,并再次使用商店中已有的数据,则可以正常工作。 我的代表我这样做:

 #pragma mark - #pragma mark NSFetchedResultsControllerDelegate methods - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.tableView endUpdates]; } 

这是从苹果的开发文档复制粘贴,任何想法是什么?

我知道这是一个古老的问题,但我有一个解决scheme:

由于fetchlimit中存在一个已知的错误,它不遵守NSFetchedResultsControllerNSFetchRequest ,所以您必须手动处理UITableViewDataSourceNSFetchedResultsControllerDelegate方法中logging的限制。

的tableView:numberOfRowsInSection:

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; NSInteger numRows = [sectionInfo numberOfObjects]; if (numRows > self.fetchedResultsController.fetchRequest.fetchLimit) { numRows = self.fetchedResultsController.fetchRequest.fetchLimit; } return numRows; } 

控制器:didChangeObject:atIndexPath:forChangeType:newIndexPath:

 - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { switch(type) { case NSFetchedResultsChangeInsert: if ([self.tableView numberOfRowsInSection:0] == self.fetchedResultsController.fetchRequest.fetchLimit) { //Determining which row to delete depends on your sort descriptors [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:self.fetchedResultsController.fetchRequest.fetchLimit - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade]; } [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; ... } } 

这是一个老问题,但我自己碰到了(在iOS 5中)。 我想你遇到了这里描述的错误: https : //devforums.apple.com/message/279576#279576 。

该线程根据您是否拥有sectionNameKeyPath来提供解决scheme。 由于我(像你)没有,答案是解耦来自fetchedResultsController的tableview。 例如,而不是使用它来确定行数:

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[[self.fetchedResultsController sections] objectAtIndex:0] numberOfObjects]; 

只是回报你所期望的:

  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return fetchLimit; 

controller:didChangeObject ,如果newIndexPath位于fetchLimit中,则只插入新对象。

这些仍然会在某些情况下崩溃,比如多个插入,或者移动超过限制,…你必须将所有更改保存到4个集合,然后计算另外4个数组并删除/更新/插入到tableView之前-[UITableView endUpdates]

有些事情(假设只有一个部分):

 NSUInteger limit = controller.fetchRequest.fetchLimit; NSUInteger current = <current section objects count>; NSMutableArray *inserts = [NSMutableArray array]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"row < %d", limit]; if (insertedIndexPaths.count) { NSUInteger deletedCount = 0; for (NSIndexPath *indexPath in insertedIndexPaths) { if (indexPath.row >= limit) continue; current++; if (current > limit) { deletedCount++; current--; [deletedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - deletedCount inSection:indexPath.section]]; } [inserts addObject:indexPath]; } } if (movedIndexPaths.count) { for (NSIndexPath *indexPath in movedIndexPaths) { if (indexPath.row >= limit) { [updatedIndexPaths addObject:[NSIndexPath indexPathForRow:limit - 1 inSection:indexPath.section]]; } else { [inserts addObject:indexPath]; } } } [updatedIndexPaths minusSet:deletedIndexPaths]; [deletedIndexPaths filterUsingPredicate:predicate]; [updatedIndexPaths filterUsingPredicate:predicate]; [_tableView insertRowsAtIndexPaths:inserts withRowAnimation:UITableViewRowAnimationFade]; [_tableView reloadRowsAtIndexPaths:[updatedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationNone]; [_tableView deleteRowsAtIndexPaths:[deletedIndexPaths allObjects] withRowAnimation:UITableViewRowAnimationFade]; [_tableView endUpdates]; deletedIndexPaths = nil; insertedIndexPaths = nil; updatedIndexPaths = nil; 

你所遇到的问题是你在调用fetchedResultsController之前调用的是充分的数据,所以它显示你所需要做的一切就是加载所有的信息,然后调用fetchedResultsController

 - (void)viewDidLoad { [super viewDidLoad]; // Loading Articles to CoreData [self loadArticle]; } - (void)ArticleDidLoadSuccessfully:(NSNotification *)notification { NSError *error; if (![[self fetchedResultsController] performFetch:&error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); // Fail } [tableView reloadData]; } 

从苹果文档: https : //developer.apple.com/reference/coredata/nsfetchrequest/1506622-fetchlimit

如果您设置提取限制,则框架尽最大努力,但不保证提高效率。 对于除SQL存储以外的每个对象存储区,执行一个取回限制的获取请求只是执行无限次获取,并抛出未被询问的行。

我在2014年6月7日向苹果提交了一个关于这个问题的bug报告。 正如其他人所指出的那样,它仍然是iOS 9和10上的一个bug。我原来的bug报告还没有得到苹果公司的反馈。 这是一个错误报告的OpenRadar副本。

这是我成功使用的一个修复程序,但它会被多次调用。 谨慎使用。

 @objc func controllerDidChangeContent(controller: NSFetchedResultsController) { tableView.endUpdates() // Only needed if you're calling tableView.beginUpdates() in controllerWillChangeContent. if controller.fetchRequest.fetchLimit > 0 && controller.fetchRequest.fetchLimit < controller.fetchedObjects?.count { controller.performFetch() // Reload the table view section here } } } 

这是我的窍门:

我调用NSManagedObjectContext实例上的'save'方法后,设置NSFetchedResultsController的委托。

  1. 在你的UIViewController上设置一个名字的观察者:eg。 '同步'
  2. 在保存你的上下文之后,发布一个名为“Sync”的通知并触发一个设置委托的函数(在你的viewcontroller中)

PS。 如果你不再需要,记得去掉那个观察者