NSFetchedResultsController ignora fetchLimit?

Tengo un NSFetchedResultsController para actualizar un UITableView con contenido de Core Data. Es un material bastante estándar, estoy seguro de que todos los has visto muchas veces, pero me encuentro con un pequeño problema. Primero aquí está mi código:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Article" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setFetchLimit:20]; NSPnetworkingicate *pnetworkingicate = [NSPnetworkingicate pnetworkingicateWithFormat:@"(folder.hidden == NO)"]; [fetchRequest setPnetworkingicate:pnetworkingicate]; 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]); } 

El problema es que inicialmente la tienda no tiene entidades, ya que descarga y sincroniza desde un service web. Lo que sucede es que NSFetchedResultsController llena la tabla con más de 150 filas de entidades de la tienda, que es cuántas vueltas vuelve el service web. Pero estoy estableciendo un límite de búsqueda de 20 que parece estar ignorando. Sin embargo, si cierro la aplicación y comienzo de nuevo con datos que ya están en la tienda, funciona bien. Soy mi delegado hago esto:

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

¿Qué es casi copyr y pegar de los documentos de desarrollo de Apple, ideas sobre qué sucede?

Sé que esta es una vieja pregunta, pero tengo una solución:

Como existe un error conocido en NSFetchedResultsController que no NSFetchRequest el NSFetchRequest de NSFetchRequest de NSFetchRequest , debe manejar manualmente la limitación de loggings dentro de los methods UITableViewDataSource y 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; } 

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

Esta es una pregunta anterior pero simplemente me topé con ella (en iOS 5). Creo que te estás topando con el error descrito aquí: https://devforums.apple.com/message/279576#279576 .

Ese hilo proporciona soluciones en function de si tiene un sectionNameKeyPath o no. Como yo (como tú) no lo hice, la respuesta es desacoplar la tableview del fetchedResultsController. Por ejemplo, en lugar de utilizarlo para determinar el número de filas:

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

solo devuelve lo que esperas:

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

Y en el controller:didChangeObject , solo inserte el nuevo object si newIndexPath está dentro de su fetchLimit.

Estos aún fallarán en algunas situaciones, como varias inserciones, o se moverán por límite, … Deberá save todos los cambios en 4 sets, y calcular otras 4 matrices y eliminar / actualizar / insert a tableView antes -[UITableView endUpdates]

Algo parecido (supongamos que solo hay una sección):

 NSUInteger limit = controller.fetchRequest.fetchLimit; NSUInteger current = <current section objects count>; NSMutableArray *inserts = [NSMutableArray array]; NSPnetworkingicate *pnetworkingicate = [NSPnetworkingicate pnetworkingicateWithFormat:@"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 filterUsingPnetworkingicate:pnetworkingicate]; [updatedIndexPaths filterUsingPnetworkingicate:pnetworkingicate]; [_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; 

El problema que tienes es que estás llamando antes de cargar fetchedResultsController carga los datos completos para que te muestre que todo lo que necesitas hacer es cargar toda la información y luego llamar a fetchedResultsController

Ejemplo

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

Desde apple doc: https://developer.apple.com/reference/conetworkingata/nsfetchrequest/1506622-fetchlimit

Si configura un límite de búsqueda, el marco hace un gran esfuerzo, pero no garantiza, para mejorar la eficiencia. Para cada almacén de objects excepto el almacén SQL, una request de captura ejecutada con un límite de captura en efecto simplemente realiza una captura ilimitada y arroja las filas no solicitadas.

Presenté un informe de errores con Apple en 2014 en iOS 6/7 sobre este problema. Como muchos otros han señalado, sigue siendo un error en iOS 9 y 10. Mi informe de error original todavía está abierto y no hay comentarios de Apple. Aquí hay una copy de OpenRadar de ese informe de error.

Aquí hay una solución que he usado con éxito, pero se llamará varias veces. Usar con precaución.

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

Este es mi truco:

Configuré el delegado de NSFetchedResultsController después del método 'save' en la instancia de NSManagedObjectContext.

  1. Establezca un observador en su UIViewController con un nombre: ej. 'Sync'
  2. después de save su context, publique una notificación con ese nombre: 'Sync' y active una function (en su viewcontroller) que configure al delegado

PD. Restring eliminar a ese observador si ya no lo necesitas.