Vista de tabla de desplazamiento asíncrono

Estoy cargando una image en una celda de vista de tabla, cada celda tiene una image. Tengo un adaptador de un par de tutoriales para el código a continuación, pero todavía estoy teniendo más lento.

Estoy cargando estas imágenes desde el directory de documentos. ¿Algún consejo o idea sobre cómo acelerar este process?

Editar código revisado:

Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.displayBeerName.text = beer.name; // did we already cache a copy of the image? if (beer.image != nil) { // good. use it. this will run quick and this will run most of the time cell.beerImage.image = beer.image; } else { // it must be the first time we've scrolled by this beer. do the expensive // image init off the main thread cell.beerImage.image = nil; // set a default value here. nil is good enough for now [self loadImageForBeer:beer atIndexPath:indexPath]; } - (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath]; beer.image = image; dispatch_sync(dispatch_get_main_queue(), ^{ UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.beerImage.image = image; }); }); } 

Su algorithm se ve bastante bien. Has evitado muchas de las trampas típicas. Si todavía tiene problemas de performance de la interfaz de usuario, sugiero un par de cosas:

  1. Debe intentar almacenar en caching sus imágenes en la memory. Puede usar NSMutableArray o NSMutableDictionary , pero ¿a la mejor manera de almacenar en caching imágenes en la aplicación ios? Caleb analiza los méritos de la class NSCache , lo que simplifica el process. Si realiza una caching de imágenes, asegúrese de responder a la presión de la memory y purgue el caching si es necesario. Puede responder a didReceiveMemoryWarning o agregarse como observador a la UIApplicationDidReceiveMemoryWarningNotification del centro de UIApplicationDidReceiveMemoryWarningNotification .

  2. Asegúrese de que las imágenes almacenadas en caching tengan el tamaño de una image en miniatura o de lo contrario, siempre tendrá un poco de tartamudeo en su interfaz de usuario (si necesita un algorithm de cambio de tamaño, háganoslo saber) y utilizará la memory innecesariamente;

  3. Cuando envíe su actualización de image a la queue principal, debe hacerlo de forma asíncrona (¿por qué tiene esa queue de background colgando y atar resources mientras espera que el bloque se envíe de vuelta a la queue principal para terminar … esto es especialmente un problema una vez que tiene un par de imágenes respaldadas durante un desplazamiento rápido); y

  4. Cuando envíe de nuevo a la queue principal, debe verificar que la celda que obtiene de cellForRowAtIndexPath no sea nil (porque si la lógica de carga de la celda se respalda demasiado (especialmente en dispositivos más lentos), teóricamente podría tener la celda en cuestión desplazada fuera de la pantalla y su algorithm podría fallar).

Utilizo un algorithm muy parecido al tuyo, con casi la misma estructura GCD (con las advertencias anteriores) y su desplazamiento es bastante suave, incluso en dispositivos más antiguos. Si quieres que publique un código, estoy contento.

Si todavía tienes problemas, el perfilador de CPU es bastante bueno para identificar los cuellos de botella y para saber dónde debes centrar tu atención. Hay algunas excelentes sesiones de WWDC disponibles en línea que se centran en cómo usar Instruments para identificar los cuellos de botella en el performance, y encontré que son muy útiles para adquirir competencia con Instruments.

Aquí está mi código. En viewDidLoad , inicializo mi caching de imágenes:

 - (void)initializeCache { self.imageCache = [[NSCache alloc] init]; self.imageCache.name = @"Custom Image Cache"; self.imageCache.countLimit = 50; } 

Y luego uso esto en mi tableView:cellForRowAtIndexPath :

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"ilvcCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // set the various cell properties // now update the cell image NSString *imageame = [self imageFilename:indexPath]; // the name of the image being retrieved UIImage *image = [self.imageCache objectForKey:imageame]; if (image) { // if we have an cachedImage sitting in memory already, then use it cell.imageView.image = image; } else { cell.imageView.image = [UIView imageNamed:@"blank_image.png"]; // the get the image in the background dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // get the UIImage UIImage *image = [self getImage:imageame]; // if we found it, then update UI if (image) { dispatch_async(dispatch_get_main_queue(), ^{ // if the cell is visible, then set the image UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; if (cell) cell.imageView.image = image; [self.imageCache setObject:image forKey:imageame]; }); } }); } return cell; } 

y

 - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; [self.imageCache removeAllObjects]; } 

Como un aparte, una optimization adicional que podría contemplar sería precargar las imágenes en caching en una queue separada, en lugar de cargar imágenes en un hilo por separado justo a time. No creo que sea necesario, ya que parece ser lo suficientemente rápido para mí, pero es una opción más para acelerar la interfaz de usuario.

No hay mucho que puedas hacer aquí por la carga inicial, eres lo más rápido posible. Si aún es demasiado lento, intente cargar imágenes más pequeñas si puede.

Un par de cosas:

Primero, tenga cuidado con -imageWithContentsOfFile, no almacenará nada en caching. Está tomando el golpe completo cada vez que carga la image, en lugar de -imageNamed que mantendrá la image caliente en algún caching. Por supuesto, puede almacenar en caching en su object de dominio, pero yo personalmente aconsejé fuertemente en contra de eso. Su huella de memory va a pasar por alto, lo que le obliga a implementar su propio mecanismo de caducidad de caching, mientras que Apple tiene una muy buena caching de imágenes a través de -imageNamed. Me sorprendería si puedes hacer un mejor trabajo que Apple en la 3 familia de dispositivos 🙂

Entonces, estás rompiendo el patrón de peso mosca de UITableView aquí:

 dispatch_sync(dispatch_get_main_queue(), ^{ cell.beerImage.image = image; beer.image = image; [cell setNeedsLayout]; }); 

Pida a la vista de tabla que proporcione su celda en un índice determinado en lugar de capturar la celda en el bloque: cuando se cargue la image, esa celda podría haberse reutilizado para otra ruta de índice y se mostrará la image en la celda equivocada.

Y no hay necesidad de -setNeedsLayout aquí, basta con cambiar la image.

Editar: ¡gritos! Me perdí lo obvio con imágenes en vista de tabla. ¿De qué tamaño son sus imágenes, qué tamaño es la vista de la image y cuál es el modo de contenido en la image? Si sus imágenes tienen un tamaño muy diferente al de la image y le pide a la vista de image que cambie el tamaño, esto ocurrirá en el hilo principal y tendrá un golpe de performance masivo allí. Cambiar el tamaño de la image a la vista de la image fuera de hilo, después de la carga (una búsqueda rápida en Google le dará el código de charts principal para hacer eso).

El paso que falta es actualizar el model con la image buscada. Tal como está, estás haciendo una nueva carga por cada celda cada vez. El model es el lugar adecuado para almacenar en caching el resultado de la carga relativamente costosa. ¿Puedes agregar una propiedad Beer.image?

Entonces, su código de configuration se vería así:

 Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.displayBeerName.text = beer.name; // did we already cache a copy of the image? if (beer.image != nil) { // good. use it. this will run quick and this will run most of the time cell.beerImage.image = beer.image; } else { // it must be the first time we've scrolled by this beer. do the expensive // image init off the main thread cell.beerImage.image = nil; // set a default value here. nil is good enough for now [self loadImageForBeer:beer atIndexPath:indexPath]; } 

Movió la lógica del cargador aquí para mayor claridad …

 - (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); dispatch_async(queue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath]; beer.image = image; dispatch_sync(dispatch_get_main_queue(), ^{ UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.beerImage.image = image; }); }); } 

Puedes echar un vistazo a esta pregunta, respondida previamente en el desbordamiento de la stack.

UIImage en la tabla de desplazamiento de slowdowns uitable Viewcell

o testing este código

 - (void)configureCell:(BeerCell *)cell atIndexPath:(NSIndexPath *)indexPath { Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath]; cell.displayBeerName.text = beer.name; UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath]; cell.beerImage.image = image; [cell setNeedsLayout]; }