NSFetchedResultsController игнорирует fetchLimit? - PullRequest
23 голосов
/ 01 февраля 2011

У меня есть 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, есть идеи, что происходит?

Ответы [ 7 ]

12 голосов
/ 31 мая 2012

Я знаю, что это старый вопрос, но у меня есть решение для него:

Поскольку в 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;
        ...
    }
}
5 голосов
/ 13 февраля 2012

Это старый вопрос, но я сам столкнулся с ним (в iOS 5).Я думаю, что вы столкнулись с ошибкой, описанной здесь: https://devforums.apple.com/message/279576#279576.

Этот поток предоставляет решения, основанные на том, есть ли у вас sectionNameKeyPath или нет.Поскольку я (как и вы) этого не сделал, ответ состоит в том, чтобы отделить табличное представление от 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.

2 голосов
/ 13 августа 2012

Они по-прежнему будут зависать в некоторых ситуациях, например, при нескольких вставках или при превышении лимита ... Вы должны сохранить все изменения в 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;
1 голос
/ 15 сентября 2016

Я отправил отчет об ошибке в 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
        }
    }
}
1 голос
/ 03 августа 2016

Из документов Apple: https://developer.apple.com/reference/coredata/nsfetchrequest/1506622-fetchlimit

Если вы установите предел выборки, фреймворк приложит максимум усилий, но не гарантирует повышения эффективности. Для каждого хранилища объектов, кроме хранилища SQL, запрос выборки, выполненный с действующим ограничением выборки, просто выполняет неограниченную выборку и отбрасывает незапрошенные строки.

1 голос
/ 02 февраля 2011

Проблема, с которой вы сталкиваетесь, заключается в том, что вы звоните перед загрузкой 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];
}   
0 голосов
/ 11 ноября 2016

Это мой трюк:

Я установил делегат NSFetchedResultsController после вызова метода 'save' для экземпляра NSManagedObjectContext.

  1. Установите наблюдателя на вашем UIViewController с именем:например.«Синхронизировать»
  2. после сохранения контекста, опубликовать уведомление с таким именем: «Синхронизировать» и вызвать функцию (в вашем viewcontroller), которая устанавливает делегата

пс.не забудьте удалить этого наблюдателя, если он вам больше не нужен

...