2009-11-29 9 views
12

Mi programa muestra una superficie de desplazamiento horizontal de azulejos con UIImageViews de izquierda a derecha. Código se ejecuta en el subproceso de interfaz de usuario para asegurarse de que UIImageViews recién visibles tienen un UIImage recién cargado asignado a ellos. La carga ocurre en un hilo de fondo.CGImage/UIImage perezosamente de carga en el hilo de interfaz de usuario hace que tartamudeo

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 mirando y finalmente se dio cuenta de que el UIImage está haciendo un proceso adicional de descanso en el hilo de interfaz de usuario cuando por primera vez se hace visible. Esto me desconcierta, ya que mi hilo de trabajo tiene un código explícito para descomprimir datos JPEG.

De todos modos, por un presentimiento escribí un código para procesar en un contexto de gráficos temporales 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 "forzar la 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 sucede 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 apreciada!

Respuesta

7

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

Puede reemplazar UIGraphicsBeginImageContext() con una llamada al 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 se está inicializando UIImage, pero si lo estás haciendo con imageNamed: o initWithFile: entonces usted podría ser capaz de obligarla a cargar mediante la carga de los datos usted mismo y luego llamar initWithData:. El tartamudeo es probablemente debido a la pereza archivo de E/S, por lo que inicializarlo con un objeto de datos a no darle la opción de leer desde un archivo.

+0

hola, gracias por tomarse el tiempo para ayudar con esto. Finalmente llegué a esta comprensión. Solo puedo imaginar qué tipo de almacenamiento en caché desagradable no es seguro para los hilos dentro de esos métodos UI *. Mi interfaz ahora está libre de traumas ... – JBx

+0

Estoy confundido ... ¿Estás diciendo que imageNamed: está causando IO de archivos vagos pero ... la creación de un objeto de datos no? ¿O está diciendo que debería tener los objetos de datos agrupados en algún lugar para extraerlos cuando corresponda? –

+1

Jasconius: si crea un objeto UIImage y le da la ruta a un archivo, es posible que no cargue toda la imagen en la memoria hasta que la dibuje. Si carga el archivo en la memoria utilizando un objeto NSData, luego pase eso a UIImage, no tiene más remedio que guardarlo en la memoria. Lo mejor depende de la situación específica. – benzado

4

que tenían el mismo problema, a pesar de que inicializa la imagen a partir de datos. (Supongo que los datos se cargan con pereza, también?) Que he tenido éxito a la fuerza de decodificación utilizando 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 
+0

Esto pareció haber funcionado muy bien para mí. ¡¡¡Gracias!!! : D – Marky

22

que he tenido el mismo problema de tartamudez, con algo de ayuda me di cuenta de la solución adecuada aquí : Non-lazy image loading in iOS

Dos cosas importantes a mencionar:

  • no utilice métodos UIKit en un trabajador de hilo. 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.Esta son las opciones que tiene que elegir (aún así es un poco claro para mí por qué):

-

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

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

+3

No puedo entender por qué tenía 0 votos ascendentes. Tu solución fue la única que funcionó para mí. Te daría 10 upvotes si pudiera. ¡¡¡GRACIAS!!! – Kalle

11

Utilicé la categoría SwapTest UIImage de @ jasamer para forzar la carga de mi UIImage grande (aproximadamente 3000x2100 px) en una cadena 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í es SwapTest UIImage categoría ... gracias de nuevo :) @jasamer

UIImage + ImmediateLoading.h Archivo de

@interface UIImage (UIImage_ImmediateLoading) 

- (UIImage*)initImmediateLoadWithContentsOfFile:(NSString*)path; 
+ (UIImage*)imageImmediateLoadWithContentsOfFile:(NSString*)path; 

@end 

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 esto es cómo creo NSOpeationQueue y establecer 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]; 
} 
+0

Todavía hay una llamada a UIImage en un hilo que no sea el hilo principal, por lo que esta solución no es segura. –

+0

Puede usar CGImageRef imageRef = CGImageCreateWithJPEGDataProvider (CGDataProviderCreateWithFilename ([path UTF8String]), NULL, NO, kCGRenderingIntentDefault) en lugar del UIImage – Tieme

+0

@JorisMans ¿cómo no es seguro llamar a UIImage fuera del hilo principal? – Hlung

Cuestiones relacionadas