NSFetchedResultsController игнорирует fetchLimit?
У меня есть NSFetchedResultsController для обновления UITableView с содержимым из Core Data. Это довольно стандартный материал, я уверен, что вы все видели много раз, но я столкнулся с небольшой проблемой. Сначала здесь мой код:
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]);
}
Проблема заключается в том, что изначально в хранилище нет сущностей, поскольку он загружается и синхронизируется с веб-сервисом. Что происходит, так это то, что NSFetchedResultsController заполняет таблицу более чем 150 строками сущностей из хранилища, а именно, сколько возвращается веб-сервис. Но я устанавливаю предел выборки 20, который, по-видимому, игнорируется. Однако, если я закрываю приложение и начинаю с данных уже в магазине, он отлично работает. Я мой делегат, я делаю это:
#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];
}
Которая в значительной степени копирует вставки из документов Apple dev, какие-то идеи о том, что происходит?
Ответы
Ответ 1
Я знаю, что это старый вопрос, но у меня есть решение для него:
Так как в NSFetchedResultsController
есть известная ошибка, которая не соблюдает fetchlimit
NSFetchRequest
, вам необходимо вручную обработать ограничение записей в ваших методах UITableViewDataSource
и NSFetchedResultsControllerDelegate
.
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;
...
}
}
Ответ 2
Это старый вопрос, но я просто наткнулся на него сам (в iOS 5). Я думаю, что вы столкнулись с описанной здесь ошибкой: https://devforums.apple.com/message/279576#279576.
Этот поток предоставляет решения на основе того, есть ли у вас разделNameKeyPath или нет. Поскольку я (как и вы) этого не сделал, ответ заключается в том, чтобы отделить табличное представление от fetchedResultsController. Например, вместо того, чтобы использовать его для определения количества строк:
- (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.
Ответ 3
Они все равно будут разбиваться в некоторых ситуациях, например, в нескольких вставках или переместить предел,...
Вы должны сохранить все изменения в 4 набора и вычислить еще 4 массива и удалить/обновить/вставить в таблицуView до -[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;
Ответ 4
Проблема заключается в том, что вы вызываете перед загрузкой 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];
}
Ответ 5
Из документа apple: https://developer.apple.com/reference/coredata/nsfetchrequest/1506622-fetchlimit
Если вы установили ограничение на выборку, инфраструктура сделает все возможное, но не гарантирует, чтобы повысить эффективность. Для каждого хранилища объектов, кроме хранилища SQL, запрос на выборку, выполняемый с лимитом выборки, просто выполняет неограниченную выборку и выбрасывает незашифрованные строки.
Ответ 6
Я подал отчет об ошибке с Apple в 2014 году на iOS 6/7 по этой проблеме. Как отмечали многие другие, это все еще ошибка в iOS 9 и 10. Мой исходный отчет об ошибках по-прежнему открыт и без обратной связи с Apple. Вот копия отчета об ошибке 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
}
}
}
Ответ 7
Это мой трюк:
Я устанавливаю делегат NSFetchedResultsController после вызова метода "save" в экземпляре NSManagedObjectContext.
- Установите наблюдателя на свой UIViewController с именем: например. 'Синхронизировать'
- после сохранения вашего контекста отправьте уведомление с этим именем: "Синхронизация" и вызовите функцию (в вашем диспетчере просмотра), которая устанавливает делегат
пс. не забудьте удалить этого наблюдателя, если он вам больше не понадобится