¿Por qué la matriz fetchedObjects de NSFetchedResultsController no siempre es homogénea?

Para no enterrar la lede, voy a abrir con mi pregunta central: ¿por qué es que la matriz fetchedObjects de mi NSFetchedResultsController suele ser homogénea, pero en raras ocasiones contiene una __NSCFString entre los objects gestionados que debería contener?

Tengo una aplicación que ha estado en producción durante mucho, mucho time. Su vista principal es una vista de tabla que contiene una list de videos, respaldados por objects gestionados de datos centrales. El controller de vista de tabla usa un NSFetchedResultsController configurado con una NSFetchRequest bastante común:

 NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:[ABCVideo entityName]]; NSString *sectionKeyPath = nil; request.fetchBatchSize = 20; NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:ABCVideoAttributes.recordingDate ascending:NO]; sectionKeyPath = @"sectionIdentifier"; request.sortDescriptors = @[sort]; request.pnetworkingicate = [NSPnetworkingicate pnetworkingicateWithFormat:@"owner = %@ and %K = %@", person, ABCVideoAttributes.serverDeleted, @(NO)]; self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:sectionKeyPath cacheName:kABCMyVideosTableViewControllerCacheKey]; 

Dado que estos videos se pueden cargar en la nube, este controller de vista de tabla recibe notifications ocasionales para actualizar las barras de progreso en las celdas de vista de tabla correspondientes a los videos que se están cargando actualmente. Dentro de esta callback, obtenemos la matriz fetchedObjects para search el video correspondiente a la notificación, de modo que la celda de vista de tabla correcta pueda actualizar su barra de progreso.

Todo esto funciona. El 99.9% del time, funciona siempre </RonBurgundy> .

Pero noté en nuestros informes de fallas de HockeyApp que hay un caso raro y raro en el que estaba obteniendo un SIGABRT que ocurre cuando mi manejador de notifications está tratando de get un filtenetworkingArrayUsingPnetworkingicate de los fetchedObjects :

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x136f24480> valueForUndefinedKey:]: this class is not key value coding-compliant for the key guid.'

Recientemente logré encontrar un caso en el que pudiera reproducir este crash de vez en cuando, y después de mucha experimentación, descubrí que la matriz fetchedObjects veces contenía algo que no era un ABCVideo : en cambio, una ranura de la matriz estaba ocupada por un __NSCFString ejemplo. Esto fue bastante sorprendente, dado que NSFetchRequestResultType es de NSManagedObjectResultType , y una cadena no es un object gestionado.

Entonces, me pregunto: ¿es un error de Core Data? ¿O mi matriz contiene un puntero que anteriormente apuntaba a una instancia de ABCVideo que se había desasignado y que la location en el montón se tomó posteriormente por una instancia de __NSCFString ? Si es este último, ¿cómo podría suceder esto? Estoy usando ARC, por lo que es difícil entender cómo uno de estos videos podría desasignarse.

Hay un error de administración de memory en -[NSFetchedResultsController fetchedObjects] con NSFastEnumeration . El object 0x136f24480 fue ABCVideo pero desasignado. Ese trozo de memory se usó para almacenar __NSCFString. El envío de posts a objects incorrectos, EXC_BAD_ACCESS y SIGABRT es el resultado común.

Este error también está en mi aplicación, pero no pude reproducirlo ni una sola vez. Si está dispuesto a compartir un proyecto de ejemplo que pueda reproducir el problema, podemos trabajar juntos para resolverlo.

Hay múltiples soluciones. La key es evitar NSFastEnumeration en fetchedObjects

 // 1 NSArray *fetchedObjects = controller.fetchedObjects for (int i = 0; i < fetchedObjects.count; ++i) { NSManagedObject *object = fetchedObjects[i]; } // 2 NSArray <id<NSFetchedResultsSectionInfo>> *sections = controller.sections; for (int s = 0; s < sections.count; ++s) { id<NSFetchedResultsSectionInfo> section = sections[s]; for (int i = 0; i < [section numberOfObjects]; ++i) { NSManagedObject *object = [controller objectAtIndexPath:[NSIndexPath indexPathForItem:i inSection:s]]; } } // 3 Fetch from NSManagedContextDirectly 

Si desafortunadamente, alguien que lee esto usa Swift, obtendrá crashes incluso llamando a fetchedObjects porque Swift convierte NSArray a Array usando NSFastEnumeration .