Cargando la imagen de CoreData en cellForRowAtIndexPath ralentiza el desplazamiento

Estoy trabajando en una UITableView que se parece mucho a la aplicación de fotos nativa de iOS: tiene muchas filas con 4 miniaturas de imágenes en cada fila. (es decir, cada UITableViewCell tiene 4 UIImageViews) Todas las miniaturas cargadas desde Core Data.

He revisado mi implementación varias veces y puedo ver mejoras en el rendimiento, pero aún así no puedo desplazarme tan fácilmente como la aplicación Photo.

Necesito asesorarme sobre cómo almacenar correctamente las fotos para un mejor rendimiento. Esto es lo que intenté:

1. Mi primer bash (muy lento cuando se desplaza)

  • Las imágenes se almacenan con el tipo Transformable en CoreData.
  • En la función cellForRow, cada imagen se obtiene de CoreData sobre la marcha.

2. Segundo bash (más rápido, pero aún un poco lento cuando se desplaza)

  • Las imágenes se almacenan con datos binarios de tipo con la opción ‘almacenamiento externo’ marcada en CoreData.
  • En la función cellForRow, cada imagen se carga primero desde Core Data y luego se almacena en NSCache en la memoria, por lo que la próxima vez que se active la función cellForRow, usaremos el UIImage de NSCache directamente si está disponible.

Después de usar NSCache para almacenar en caché las imágenes cargadas desde CoreData, el desplazamiento es visiblemente más rápido, pero dado que las imágenes aún tienen que cargarse desde CoreData cuando aún no están disponibles en NSCache, el desplazamiento seguirá siendo desigual de vez en cuando.

Entonces, debe haber una mejor manera, podría precargar todas las imágenes en la memoria, pero dado que podría haber un gran número o filas de imágenes, no planifiqué precargar las imágenes.

¿Qué más puedo hacer para cargar la imagen más rápido en cellForRowAtIndexPath?

Para mantener el desplazamiento sin problemas, independientemente de dónde provengan sus datos, debe buscar sus datos en un hilo separado y solo actualizar la UI cuando tenga los datos en la memoria. Grand Central Despatch es el camino a seguir. Aquí hay un esqueleto que supone que tiene un diccionario self.photos con una referencia de texto a un archivo de imagen. La miniatura de la imagen puede o no estar cargada en un diccionario en vivo; puede o no estar en un caché de sistema de archivos; de lo contrario, se obtiene de una tienda en línea. Podría usar Core Data, pero la clave para desplazarse sin problemas es que no espere los datos de donde vengan.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Photo Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; //identify the data you are after id photo = [self.photos objectAtIndex:indexPath.row]; // Configure the cell based on photo id dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //move to an asynchronous thread to fetch your image data UIImage* thumbnail = //get thumbnail from photo id dictionary (fastest) if (!thumbnail) { //if it's not in the dictionary thumbnail = //get it from the cache (slower) // update the dictionary if (!thumbnail) { //if it's not in the cache thumbnail = //fetch it from the (online?) database (slowest) // update cache and dictionary } } } if (thumbnail) { dispatch_async(dispatch_get_main_queue(), ^{ //return to the main thread to update the UI if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) { //check that the relevant data is still required UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath]; //get the correct cell (it might have changed) [[correctCell imageView] setImage:thumbnail]; [correctCell setNeedsLayout]; } }); } }); return cell; } 

Si está utilizando algún tipo de gestor de tienda de imágenes singleton, esperaría que el administrador se ocupe de los detalles del acceso a la caché / base de datos, lo que simplifica este ejemplo.

Esta parte

  UIImage* thumbnail = //get thumbnail from photo id dictionary (fastest) if (!thumbnail) { //if it's not in the dictionary thumbnail = //get it from the cache (slower) // update the dictionary if (!thumbnail) { //if it's not in the cache thumbnail = //fetch it from the (online?) database (slowest) // update cache and dictionary } } 

sería reemplazado con algo así como

  UIImage* thumbnail = [[ImageManager singleton] getImage]; 

(No usaría un bloque de finalización ya que efectivamente está proporcionando uno en GCD cuando regrese a la cola principal)

He usado una clase llamada JMImageCache para almacenar en caché la imagen en el disco y luego solo almaceno la URL local (y la URL remota si lo desea) en CoreData. También puede generar una miniatura que también guarda en el disco y almacenar la URL de la miniatura junto con la URL de la imagen en los datos centrales, y usar la miniatura en la vista de tabla.

Agregue una subclase de NSValueTransformer para la imagen en los datos centrales

Código de la siguiente manera:

 + (BOOL)allowsReverseTransformation { return YES; } + (Class)transformedValueClass { return [NSData class]; } - (id)transformedValue:(id)value { return UIImageJPEGRepresentation(value, 0.5); } - (id)reverseTransformedValue:(id)value { return [[UIImage alloc] initWithData:value]; }