Pregunta

Mi programa muestra una superficie de desplazamiento horizontal en mosaico con UIImageViews de izquierda a derecha. El código se ejecuta en el subproceso de la interfaz de usuario 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 a medida que cada imagen se hace visible. Al principio pensé que mi trabajador de fondo 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 perezoso adicional en el hilo de la interfaz de usuario cuando se hace 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, en una corazonada escribí un código para representar en un contexto gráfico temporal en el hilo de fondo y, efectivamente, el tartamudeo desapareció. El UIImage ahora se está precargando en mi hilo de trabajo. Hasta ahora todo bien.

El problema es que mi nueva " fuerza de carga diferida de imagen " El método no es confiable. Causa EXC_BAD_ACCESS intermitente. No tengo idea de lo que UIImage está haciendo realmente detrás de escena. 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: ¿Puedo hacer esto en un hilo que no sea el hilo de la interfaz de usuario? PREGUNTA 2: ¿Qué es el UIImage en realidad " precarga " ;? PREGUNTA 3: ¿Cuál es la forma oficial de resolver este problema?

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

¿Fue útil?

Solución

Los métodos UIGraphics * están diseñados para ser llamados únicamente desde el hilo principal. Probablemente sean la fuente de tus problemas.

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


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

Otros consejos

He tenido el mismo problema de tartamudeo, con un poco de ayuda descubrí la solución adecuada aquí: Carga de imagen no perezosa en iOS

Dos cosas importantes para mencionar:

  • No utilice métodos UIKit en un subproceso de trabajo. Utilice 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 muestra aquí: SwapTest , tiene aproximadamente el mismo rendimiento que la aplicación 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 3000x2100 px) en un hilo de trabajo (con NSOperationQueue). Esto reduce el tiempo de tartamudeo al configurar la imagen en el 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 perezosamente?) He logrado forzar la decodificación con 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top