CGImage / UIImagez cargándose perezosamente en el hilo de la interfaz de usuario causa tartamudeo

Mi progtwig muestra una superficie de desplazamiento horizontal en mosaico con UIImageViews de izquierda a derecha. El código se ejecuta en el subproceso de UI para garantizar que los UIImageViews recientemente visibles tengan asignado un UIImage recién cargado. La carga ocurre en un hilo de fondo.

Todo funciona casi bien, excepto que hay un tartamudeo cuando cada imagen se vuelve visible. Al principio, pensé que mi asistente social estaba bloqueando algo en el hilo de la interfaz de usuario. Pasé mucho tiempo mirándolo y finalmente me di cuenta de que el UIImage está haciendo un procesamiento extra perezoso en el subproceso de la interfaz de usuario cuando se vuelve visible por primera vez. Esto me desconcierta, ya que mi hilo de trabajo tiene un código explícito para descomprimir datos JPEG.

De todos modos, como presentimiento, escribí un código para procesar en un contexto gráfico temporal en el hilo de fondo y, efectivamente, el tartamudeo desapareció. El UIImage ahora está siendo precargado en mi hilo de trabajo. Hasta aquí todo bien.

El problema es que mi nuevo método de “fuerza de carga lenta de la imagen” no es confiable. Causa intermitente EXC_BAD_ACCESS. No tengo idea de qué hace realmente UIImage entre bastidores. Quizás esté descomprimiendo los datos JPEG. De todos modos, el método es:

+ (void)forceLazyLoadOfImage: (UIImage*)image { CGImageRef imgRef = image.CGImage; CGFloat currentWidth = CGImageGetWidth(imgRef); CGFloat currentHeight = CGImageGetHeight(imgRef); CGRect bounds = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); CGAffineTransform transform = CGAffineTransformIdentity; CGFloat scaleRatioX = bounds.size.width / currentWidth; CGFloat scaleRatioY = bounds.size.height / currentHeight; UIGraphicsBeginImageContext(bounds.size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextScaleCTM(context, scaleRatioX, -scaleRatioY); CGContextTranslateCTM(context, 0, -currentHeight); CGContextConcatCTM(context, transform); CGContextDrawImage(context, CGRectMake(0, 0, currentWidth, currentHeight), imgRef); UIGraphicsEndImageContext(); } 

Y el EXC_BAD_ACCESS ocurre en la línea CGContextDrawImage. PREGUNTA 1: ¿Se me permite hacer esto en un hilo que no sea el del UI? PREGUNTA 2: ¿Qué es realmente el UIImage “precargado”? PREGUNTA 3: ¿Cuál es la forma oficial de resolver este problema?

¡Gracias por leer todo eso, cualquier consejo sería muy apreciado!

Los métodos UIGraphics* están diseñados para ser llamados solo desde el hilo principal. Probablemente sean la fuente de tu problema.

Puede reemplazar UIGraphicsBeginImageContext() con una llamada a CGBitmapContextCreate() ; es un poco más complicado (necesita crear un espacio de color, calcular el tamaño adecuado del búfer para crear y asignarlo usted mismo). Los métodos CG* están bien para ejecutar desde un hilo diferente.


No estoy seguro de cómo está inicializando UIImage, pero si lo está haciendo con imageNamed: o initWithFile: entonces puede obligarlo a cargar cargando los datos usted mismo y luego llamando initWithData: El tartamudeo probablemente se deba a una E / S de archivos perezosa, por lo que inicializarlo con un objeto de datos no le dará la opción de leer desde un archivo.

He tenido el mismo problema de tartamudeo, con algo de ayuda encontré la solución adecuada aquí: carga de imágenes no vagas en iOS

Dos cosas importantes para mencionar:

  • No use los métodos UIKit en un hilo de trabajo. Usa CoreGraphics en su lugar.
  • Incluso si tiene un hilo de fondo para cargar y descomprimir imágenes, todavía tendrá un poco de tartamudeo si usa la máscara de bits incorrecta para su CGBitmapContext. Estas son las opciones que tiene que elegir (todavía no está claro por qué):

 CGBitmapContextCreate(imageBuffer, width, height, 8, width*4, colourSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 

He publicado un proyecto de ejemplo aquí: SwapTest , tiene aproximadamente el mismo rendimiento que la aplicación de Fotos de Apple para cargar / mostrar imágenes.

Utilicé la categoría SwapTest UIImage de @ jasamer para forzar la carga de mi gran UIImage (aproximadamente 3000×2100 px) en un hilo de trabajo (con NSOperationQueue). Esto reduce el tiempo de tartamudeo cuando se establece la imagen en UIImageView a un valor aceptable (aproximadamente 0,5 segundos en iPad1).

Aquí está la categoría SwapTest UIImage … gracias de nuevo @jasamer 🙂

Archivo UIImage + ImmediateLoading.h

 @interface UIImage (UIImage_ImmediateLoading) - (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path; + (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path; @end 

Archivo UIImage + ImmediateLoading.m

 #import "UIImage+ImmediateLoading.h" @implementation UIImage (UIImage_ImmediateLoading) + (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path { return [[[UIImage alloc] initImmediateLoadWithContentsOfFile: path] autorelease]; } - (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path { UIImage *image = [[UIImage alloc] initWithContentsOfFile:path]; CGImageRef imageRef = [image CGImage]; CGRect rect = CGRectMake(0.f, 0.f, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); CGContextRef bitmapContext = CGBitmapContextCreate(NULL, rect.size.width, rect.size.height, CGImageGetBitsPerComponent(imageRef), CGImageGetBytesPerRow(imageRef), CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little ); //kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little are the bit flags required so that the main thread doesn't have any conversions to do. CGContextDrawImage(bitmapContext, rect, imageRef); CGImageRef decompressedImageRef = CGBitmapContextCreateImage(bitmapContext); UIImage* decompressedImage = [[UIImage alloc] initWithCGImage: decompressedImageRef]; CGImageRelease(decompressedImageRef); CGContextRelease(bitmapContext); [image release]; return decompressedImage; } @end 

Y así es como creo NSOpeationQueue y configuro la imagen en el hilo principal …

 // Loads low-res UIImage at a given index and start loading a hi-res one in background. // After finish loading, set the hi-res image into UIImageView. Remember, we need to // update UI "on main thread" otherwise its result will be unpredictable. -(void)loadPageAtIndex:(int)index { prevPage = index; //load low-res imageViewForZoom.image = [images objectAtIndex:index]; //load hi-res on another thread [operationQueue cancelAllOperations]; NSInvocationOperation *operation = [NSInvocationOperation alloc]; filePath = [imagesHD objectAtIndex:index]; operation = [operation initWithTarget:self selector:@selector(loadHiResImage:) object:[imagesHD objectAtIndex:index]]; [operationQueue addOperation:operation]; [operation release]; operation = nil; } // background thread -(void)loadHiResImage:(NSString*)file { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"loading"); // This doesn't load the image. //UIImage *hiRes = [UIImage imageNamed:file]; // Loads UIImage. There is no UI updating so it should be thread-safe. UIImage *hiRes = [[UIImage alloc] initImmediateLoadWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType: nil]]; [imageViewForZoom performSelectorOnMainThread:@selector(setImage:) withObject:hiRes waitUntilDone:NO]; [hiRes release]; NSLog(@"loaded"); [pool release]; } 

Tuve el mismo problema, aunque inicialicé la imagen usando datos. (¿Supongo que los datos también se cargan de forma perezosa?) Logré forzar la deencoding usando la siguiente categoría:

 @interface UIImage (Loading) - (void) forceLoad; @end @implementation UIImage (Loading) - (void) forceLoad { const CGImageRef cgImage = [self CGImage]; const int width = CGImageGetWidth(cgImage); const int height = CGImageGetHeight(cgImage); const CGColorSpaceRef colorspace = CGImageGetColorSpace(cgImage); const CGContextRef context = CGBitmapContextCreate( NULL, /* Where to store the data. NULL = don't care */ width, height, /* width & height */ 8, width * 4, /* bits per component, bytes per row */ colorspace, kCGImageAlphaNoneSkipFirst); CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); CGContextRelease(context); } @end 
    Intereting Posts