Domanda

Il mio programma mostra una superficie di scorrimento orizzontale piastrellata con UIImageViews da sinistra a destra. Il codice viene eseguito sul thread dell'interfaccia utente per garantire che a UIImageViews appena visibili sia assegnato un UIImage appena caricato. Il caricamento avviene su un thread in background.

Tutto funziona quasi bene, tranne che c'è una balbuzie mentre ogni immagine diventa visibile. All'inizio pensavo che il mio background worker stesse bloccando qualcosa nel thread dell'interfaccia utente. Ho trascorso molto tempo a guardarlo e alla fine mi sono reso conto che UIImage sta eseguendo un'ulteriore elaborazione pigra sul thread dell'interfaccia utente quando diventa visibile per la prima volta. Questo mi confonde, poiché il mio thread di lavoro ha un codice esplicito per decomprimere i dati JPEG.

Ad ogni modo, su un sospetto, ho scritto del codice per renderlo in un contesto grafico temporaneo sul thread di sfondo e - abbastanza sicuro, la balbuzie è sparita. UIImage ora è precaricato sul mio thread di lavoro. Fin qui tutto bene.

Il problema è che il mio nuovo "impone un carico pigro di immagine" il metodo non è affidabile. Causa EXC_BAD_ACCESS intermittente. Non ho idea di cosa stia facendo effettivamente UIImage dietro le quinte. Forse sta decomprimendo i dati JPEG. Ad ogni modo, il metodo è:

+ (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();
}

E EXC_BAD_ACCESS si verifica sulla riga CGContextDrawImage. DOMANDA 1: Posso farlo su un thread diverso dal thread dell'interfaccia utente? DOMANDA 2: Che cos'è effettivamente UIImage "pre-caricamento"? DOMANDA 3: Qual è il modo ufficiale per risolvere questo problema?

Grazie per aver letto tutto ciò, ogni consiglio sarebbe molto apprezzato!

È stato utile?

Soluzione

I metodi UIGraphics * sono progettati per essere chiamati solo dal thread principale. Probabilmente sono la fonte del tuo problema.

Puoi sostituire UIGraphicsBeginImageContext () con una chiamata a CGBitmapContextCreate () ; è un po 'più coinvolto (è necessario creare uno spazio colore, capire il buffer delle dimensioni giuste per creare e allocarlo da soli). I metodi CG * possono essere eseguiti da un thread diverso.


Non sono sicuro di come stai inizializzando UIImage, ma se lo stai facendo con imageNamed: o initWithFile: , potresti essere in grado di forzarlo caricare caricando i dati da soli e quindi chiamando initWithData: . La balbuzie è probabilmente dovuta all'I / O di file pigro, quindi l'inizializzazione con un oggetto dati non gli darà la possibilità di leggere da un file.

Altri suggerimenti

Ho avuto lo stesso problema di balbuzie, con un po 'di aiuto ho trovato la soluzione corretta qui: Caricamento di immagini non pigre in iOS

Due cose importanti da menzionare:

  • Non utilizzare i metodi UIKit in un thread di lavoro. Usa invece CoreGraphics.
  • Anche se hai un thread in background per caricare e decomprimere le immagini, avrai comunque un po 'di stutter se usi la maschera di bit sbagliata per il tuo CGBitmapContext. Queste sono le opzioni che devi scegliere (non mi è ancora chiaro perché):

-

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

Ho pubblicato un progetto di esempio qui: SwapTest , ha circa la stessa performance dell'app Foto di Apple per il caricamento / la visualizzazione di immagini.

Ho usato la categoria UIImage SwapTest di @ jasamer per forzare il caricamento del mio grande UIImage (circa 3000x2100 px) in un thread di lavoro (con NSOperationQueue). Ciò riduce il tempo di balbuzie quando si imposta l'immagine in UIImageView a un valore accettabile (circa 0,5 secondi su iPad1).

Ecco la categoria UIImage di SwapTest ... grazie ancora @jasamer :)

File UIImage + ImmediateLoading.h

@interface UIImage (UIImage_ImmediateLoading)

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

@end

UIImage + file 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

Ed è così che creo NSOpeationQueue e imposto l'immagine sul thread principale ...

// 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];
}

Ho avuto lo stesso problema, anche se ho inizializzato l'immagine usando i dati. (Suppongo che anche i dati vengano caricati pigramente?) Sono riuscito a forzare la decodifica utilizzando la seguente categoria:

@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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top