Domanda NSFetchedResultsController ignora fetchLimit?


Ho un NSFetchedResultsController per aggiornare un UITableView con contenuti da Core Data. Sono cose piuttosto standard, sono sicuro che hai visto molte volte, ma sto incontrando un piccolo problema. Prima ecco il mio codice:

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]);
 }

Il problema è che inizialmente il negozio non ha entità in quanto scarica e si sincronizza da un webservice. Quello che succede è che NSFetchedResultsController riempie la tabella con oltre 150 righe di entità dallo store, che è il numero di restituzioni del servizio web. Ma sto impostando un limite di 20 di recupero che sembra ignorare. Tuttavia, se chiudo l'app e ricomincio con i dati già presenti nello store, funziona correttamente. Sono un mio delegato lo faccio:

#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]; 
}

Quale è praticamente copia-incolla dai documenti di sviluppo di Apple, qualche idea su cosa sta succedendo?


22
2018-02-01 02:16


origine


risposte:


So che questa è una vecchia domanda, ma ho una soluzione per questo:

Poiché c'è un bug noto in NSFetchedResultsController quello non onora il fetchlimit del NSFetchRequest, devi gestire manualmente la limitazione dei record all'interno del tuo UITableViewDataSource e NSFetchedResultsControllerDelegate metodi.

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;
}

Controller: 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;
        ...
    }
}

11
2018-05-31 16:49



Questa è una vecchia domanda, ma mi sono imbattuto in esso da solo (in iOS 5). Penso che tu stia correndo nel bug descritto qui: https://devforums.apple.com/message/279576#279576.

Quel thread fornisce soluzioni in base al fatto che tu abbia un sectionNameKeyPath o meno. Dal momento che io (come te) non l'ho fatto, la risposta è disaccoppiare la tableview dal fetchedResultsController. Ad esempio, anziché utilizzarlo per determinare il numero di righe:

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

restituisci solo ciò che ti aspetti:

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

E in controller:didChangeObject, inserisci il nuovo oggetto solo se newIndexPath è all'interno di fetchLimit.


5
2018-02-13 17:12



Questi si bloccano ancora in alcune situazioni, come diversi inserti, o si spostano oltre il limite, ... Devi salvare tutte le modifiche in 4 set e calcolare altri 4 array e cancellare / aggiornare / inserire in tableView prima -[UITableView endUpdates]

Alcune cose come (supponiamo che ci sia solo una sezione):

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;

2
2017-08-13 03:49



Il problema che hai è che stai chiamando prima di caricare fetchedResultsController carica i dati completi così ti mostra che tutto ciò che devi fare è caricare tutte le informazioni e poi chiamare fetchedResultsController

Esempio

- (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];
}   

1
2018-02-02 15:11



Da Apple doc: https://developer.apple.com/reference/coredata/nsfetchrequest/1506622-fetchlimit

Se imposti un limite di recupero, il framework fa il massimo sforzo, ma non garantisce, per migliorare l'efficienza. Per ogni archivio oggetti ad eccezione dell'archivio SQL, una richiesta di recupero eseguita con un limite di recupero in effetti esegue semplicemente un recupero illimitato e getta via le righe non richieste.


1
2017-08-03 15:43



Ho presentato una segnalazione di bug con Apple nel 2014 su iOS 6/7 su questo problema. Come molti altri hanno notato, è ancora un bug su iOS 9 e 10. Il mio bug report originale è ancora aperto anche senza feedback da parte di Apple. Ecco una copia OpenRadar di quel bug report.

Ecco una correzione che ho usato con successo ma verrà chiamata più volte. Usare con cautela.

@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
2017-09-15 17:57



Questo è il mio trucco:

Ho impostato il delegato NSFetchedResultsController dopo che è stato chiamato il metodo 'save' sull'istanza NSManagedObjectContext.

  1. Imposta un osservatore sul tuo UIViewController con un nome: es. 'Sync'
  2. dopo aver salvato il tuo contesto, pubblica una notifica con quel nome: "Sincronizza" e attiva una funzione (nel tuo viewcontroller) che imposta il delegato

ps. ricorda di rimuovere quell'osservatore se non ne hai più bisogno


0
2017-11-11 18:26