2012-09-14 13 views
8

Después de leer docenas de preguntas similares, me gustaría comenzar con esta afirmación: "Hice el delegado del NSFetchedResultsController, no funcionó". .NSFetchedResultsController no ve nuevos insertos/elimina los valores obtenidos después de la actualización

Tengo un TableViewController simple cuyas celdas están llenas de NSFetchedResultsController. Aquí está el código FRC init:

@property (strong, nonatomic) NSFetchedResultsController *frc; 

... 

- (NSFetchedResultsController *)frc 
{ 
    if (!_frc) 
    { 
     NSError *error = nil; 
     NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product]; 
     request.sortDescriptors = [NSArray arrayWithObjects: 
           [NSSortDescriptor sortDescriptorWithKey:@"product_group.product_group_name" ascending:YES], 
           [NSSortDescriptor sortDescriptorWithKey:f_product_name ascending:YES], 
           nil]; 
     _frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[DTGGlobalSettings sharedInstance].moc sectionNameKeyPath:@"product_group.product_group_name" cacheName:nil]; 
     _frc.delegate = self; 
     [_frc performFetch:&error]; 
     if (error) 
     { 
      NSLog(@"Error while fetching products: %@!", error.userInfo); 
     } 
    } 

    return _frc; 
} 

También he métodos de delegado NSFetchedResultsController implementado con algunas etiquetas de depuración:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates. 
    NSLog(@"controllerWillChangeContent"); 
    [self.tableView beginUpdates]; 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

    UITableView *tableView = self.tableView; 

switch(type) { 

    case NSFetchedResultsChangeInsert: 
     NSLog(@"didChangeObject - insert"); 
     [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeDelete: 
     NSLog(@"didChangeObject - delete"); 
     [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeUpdate: 
     NSLog(@"didChangeObject - update"); 
     [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 
     break; 

    case NSFetchedResultsChangeMove: 
     NSLog(@"didChangeObject - move"); 
     [tableView deleteRowsAtIndexPaths:[NSArray 
              arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     [tableView insertRowsAtIndexPaths:[NSArray 
              arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
} 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { 

switch(type) { 

    case NSFetchedResultsChangeInsert: 
     NSLog(@"didChangeSection - insert"); 
     [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeDelete: 
     NSLog(@"didChangeSection - delete"); 
     [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
} 
} 


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { 
// The fetch controller has sent all current change notifications, so tell the table view to process all updates. 
NSLog(@"controllerDidChangeContent"); 
[self.tableView endUpdates]; 
} 

En mi navigationbar tengo el botón de actualización, su función es la de encender el catálogo de productos método que hace lo siguiente: 1. Obtiene productos del servidor MySQL remoto 2. Si el producto con ID no existe, lo crea y rellena todos los campos 3. Si existe, todos los campos se actualizan de acuerdo con el valores obtenidos

Después de la fila 2 y 3 se guarda la tienda. Utilizo NSManagedObjectContext para todas las operaciones de CoreData en la aplicación.

Cuando inicio la aplicación por primera vez, TVC está vacío, porque todavía no se han recuperado los productos. Cuando presiono Actualizar, los nuevos productos se extraen y se insertan en ManagedObjectContext, pero NSManagedObjectContext no ve/escucha ningún cambio: no se invocan métodos de delegado, por lo que no se agregan nuevas filas a TVC. Cuando reinicio la aplicación, los nuevos productos están en su lugar en el TVC sin ningún problema.

Si presiono el botón Actualizar una vez más cuando hay algunos productos allí, después de una breve demora, todos desaparecen. Algunas depuraciones me mostraron que si sucede después de actualizar los campos de las entidades (en realidad los valores son los mismos) y guardar la tienda. Los métodos delegar esta vez funcionan como un amuleto y para cada controlador de entidad actualizado y guardado: didChangeObject: atIndexPath: forChangeType: newIndexPath se llama con forChangeType = NSFetchedResultsChangeDelete.

Pregunta n. ° 1: ¿por qué NSFetchedResultsController no ve las entidades insertadas sin reiniciar la aplicación? Pregunta # 2: ¿por qué NSFetchedResultsController marca entidades ya recuperadas como "no existentes" (?) Y las elimina de TVC después de guardar en la tienda?

Le agradecería cualquier ayuda e ideas, siendo la lucha con este problema para toda la semana pasada; (

UPD1 (por Jody): Aquí están algunos detalles que utilizan DTGGlobalSettings clases personalizadas para compartir algunas vars. . a través de la aplicación Así es como me init su propiedad sharedInstance:

+(DTGGlobalSettings *)sharedInstance 
{ 
    static DTGGlobalSettings *myInstance = nil; 

    if (nil == myInstance) 
    { 
     myInstance = [[[self class] alloc] init]; 
     // set values here 
     // try to acces MOC to init CoreData 
     [myInstance moc];   
     myInstance.baseURL = @"http://local.app/"; 
} 
return myInstance; 
} 

a init pila CoreData utilicé un ejemplo de la documentación de Apple, porque por alguna razón no veo la casilla de verificación "Usar CoreData" al crear un nuevo proyecto. Estoy seguro de haberlo visto antes en Xc oda, pero ahora no lo hago; ((Xcode 4.4.1):

#pragma mark Core Data stack 

- (NSManagedObjectContext *) moc { 

if (_moc != nil) { 
    return _moc; 
} 
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
if (coordinator != nil) { 
    _moc = [[NSManagedObjectContext alloc] init]; 
    [_moc setPersistentStoreCoordinator: coordinator]; 
} 
return _moc; 
} 


- (NSManagedObjectModel *)managedObjectModel { 

if (_managedObjectModel != nil) { 
    return _managedObjectModel; 
} 
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];  
return _managedObjectModel; 
} 


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 

if (_persistentStoreCoordinator != nil) { 
    return _persistentStoreCoordinator; 
} 
NSURL *storeUrl = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 
storeUrl = [storeUrl URLByAppendingPathComponent:DOC_NAME]; 

NSError *error; 
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]]; 

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) { 
    NSLog(@"Error adding a store to the coordinator: %@, %@", error, error.userInfo); 
}   
return _persistentStoreCoordinator; 
} 

-(void)saveDataStore 
{ 
    NSError *error; 
    if (![self.moc save:&error]) NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
} 

El 'moc' (ManagedObjectContext) Propiedad de DTGGlobalSettings se utiliza entonces por todas partes en la aplicación donde necesito acceso a CoreData.

Ahora a la segunda parte, actualizando la base de datos.Obtengo algunas entidades nuevas en formato JSON desde un servidor web remoto, revisando el diccionario resultante y llamando al método createFromDictionary para cada entidad que se encuentra en los resultados de la solicitud. Aquí está el código:

+(Product *)createFromDictionary:(NSDictionary *)entityData inContext:(NSManagedObjectContext *)moc performUpdate:(BOOL)update 
{ 
Product *result; 
if (update) 
{ 
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product]; 
    request.predicate = [NSPredicate predicateWithFormat:@"%K = %@", f_product_id, [entityData objectForKey:f_product_id]]; 
    result = [[DTGGlobalSettings sharedInstance].moc executeFetchRequest:request error:nil].lastObject; 
    if (!result) 
    { 
     NSLog(@"Error updating Product ID %@! Cannot fetch entity.", [entityData objectForKey:f_product_id]); 
     return nil; 
    } 
} else 
{ 
    result = [NSEntityDescription insertNewObjectForEntityForName:e_product inManagedObjectContext:[DTGGlobalSettings sharedInstance].moc]; 
} 

result.product_id = [[DTGGlobalSettings sharedInstance].numFormatter numberFromString:[entityData objectForKey:f_product_id]]; 
result.product_image = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[DTGGlobalSettings sharedInstance].baseURL stringByAppendingString:[entityData objectForKey:f_product_image]]]]; 
result.product_name = [entityData objectForKey:f_product_name]; 

return result; 
} 

Cuando me quedo sin entidades fethed, llamo [[DTGGlobalSettings sharedInstance] saveDataStore] (véase más arriba). En este punto, desaparecen las filas existentes en TVC (si hubo alguna). En caso de que agregara algunas entidades nuevas, en el momento de guardar nada sucede, es decir, TVC no actualiza sus filas.

+0

Ha publicado el código incorrecto. Los aspectos interesantes son cómo se importan los datos en su base de datos y cómo se configura la pila de datos básicos para la importación y el controlador de resultados obtenidos. –

+0

Agregué algunos detalles que solicitó en UPD1. Espero que ahora esté más claro cómo terminé con esto ... – Arseniy

+0

¿Alguna idea sobre la actualización? – Arseniy

Respuesta

14

En realidad, creo que su primera publicación previa a la edición tenía toda la información necesaria. Simplemente no lo leí (no me gusta tener que seguir desplazándome, leer largas líneas de código y, a veces, simplemente no hacerlo, es malo).

Parece que su problema está relacionado con los descriptores de clasificación de FRC.

FRC no se ocupa bien de mantener las descripciones de ordenación de relaciones actualizadas muy bien.

Algunos han argumentado que así es como está diseñado. Yo argumento que es un error.

Ver este post Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view para mi toma. Con suerte, verá las similitudes con su caso.

+0

¡Eso es increíble! ¡Simplemente eliminé rápidamente las descripciones de clasificación y la ruta del nombre de la sección y funcionó! Nunca imaginaría que esto funciona así;) ¡Gracias! – Arseniy

+0

¿este problema sigue ahí? –

+0

Sí. https://insanitysauce.files.wordpress.com/2013/09/bsxgx3vciaax5g5.jpg –

1

Acabo de pasar el tiempo con el mismo problema y descubrí lo siguiente: cuando el sectionNameKeyPath se refería a un campo de relación que podría tomar el valor nil, la actualización de FRC no funcionaría al insertar objetos.

Esto puede estar relacionado con una advertencia en la consola de Xcode diciendo que la sección se crearía para objetos con ese campo establecido en nil. Supongo que esa sección se equivoca con el funcionamiento interno de FRC.

Cuestiones relacionadas