Подтвердить что ты не робот

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, какие-то идеи о том, что происходит?

4b9b3361

Ответ 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 с именем: например. 'Синхронизировать'
  • после сохранения вашего контекста отправьте уведомление с этим именем: "Синхронизация" и вызовите функцию (в вашем диспетчере просмотра), которая устанавливает делегат

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