Pergunta

Meu programa exibe uma superfície de rolagem horizontal lado a lado com UIImageViews da esquerda para a direita. Código é executado no segmento interface do usuário para garantir que UIImageViews recém-visíveis têm uma UIImage recém-carregado atribuído a eles. O carregamento acontece em uma discussão de fundo.

Tudo funciona quase bem, exceto que há uma gagueira como cada imagem se torna visível. No começo eu pensei que o meu trabalho de fundo estava trancando algo no segmento. Passei muito tempo olhando para ele e finalmente percebeu que o UIImage está fazendo algum processamento extra preguiçoso no thread UI quando se torna visível pela primeira vez. Este quebra-me, desde o meu segmento de trabalho tem código explícito para descomprimir dados JPEG.

De qualquer forma, em um palpite eu escrevi algum código para tornar em um contexto gráfico temporário no segmento de segundo plano e - com certeza, a gagueira foi embora. O UIImage está agora a ser pré-carregado no meu segmento de trabalho. Até aqui tudo bem.

O problema é que a minha "força de carga preguiçosa de imagem" novo método não é confiável. Isso faz com que EXC_BAD_ACCESS intermitente. Eu não tenho nenhuma idéia do que UIImage está realmente fazendo nos bastidores. Talvez seja descomprimir os dados JPEG. De qualquer forma, o método é:

+ (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 o EXC_BAD_ACCESS acontece na linha CGContextDrawImage. PERGUNTA 1: Posso fazer isso em um segmento diferente do thread UI? Pergunta 2: Qual é o UIImage verdade "pré-carregamento"? PERGUNTA 3:? O que é a forma oficial de resolver este problema

Obrigado por ler tudo isso, qualquer conselho seria muito apreciada!

Foi útil?

Solução

Os métodos UIGraphics* são projetados para ser chamado a partir de apenas o segmento principal. Eles são provavelmente a fonte de seu problema.

Você pode substituir UIGraphicsBeginImageContext() com uma chamada para CGBitmapContextCreate(); é um pouco mais envolvidos (você precisa criar um espaço de cor, figura o buffer tamanho certo para criar e alocar it yourself). Os métodos CG* são muito bem para ser executado a partir de um segmento diferente.


Eu não sei como você está inicializando UIImage, mas se você está fazendo isso com imageNamed: ou initWithFile: então você pode ser capaz de forçá-lo a carga, carregar os dados a si mesmo e, em seguida, chamando initWithData:. A gagueira é provavelmente devido ao arquivo preguiçoso I / O, assim inicializando-lo com um objeto de dados não vai dar-lhe a opção de leitura de um arquivo.

Outras dicas

Eu tive o mesmo problema de gagueira, com alguma ajuda eu descobri a solução adequada aqui: imagem de carga não-preguiçoso no iOS

Duas coisas importantes a menção:

  • Não use métodos UIKit em um trabalhador-thread. Use CoreGraphics vez.
  • Mesmo se você tem uma discussão de fundo para o carregamento e imagens descompressão, você ainda vai ter um pouco de gagueira se você usar a máscara de bits errado para o seu CGBitmapContext. Isto são as opções que você tem que escolher (que ainda é um pouco claro para mim por que):

-

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

Eu publiquei um projeto de exemplo aqui: SwapTest , tem sobre a mesma performace como fotos Maçãs app para carga / exibir imagens.

Eu usei @ de jasamer categoria SwapTest UIImage a carga força o meu grande UIImage (cerca de 3000x2100 px) em um segmento de trabalho (com NSOperationQueue). Isto reduz o tempo gaguez quando definindo a imagem para o UIImageView para um valor aceitável (cerca de 0,5 segundos em iPad1).

Aqui é a categoria SwapTest UIImage ... obrigado novamente @jasamer:)

arquivo UIImage + ImmediateLoading.h

@interface UIImage (UIImage_ImmediateLoading)

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

@end

arquivo 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

E é assim que eu criar NSOpeationQueue e definir a imagem no segmento 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];
}

Eu tive o mesmo problema, mesmo que eu inicializado a imagem utilizando dados. (Eu acho que os dados são carregados preguiçosamente, também?) Eu tenho conseguido forçar a decodificação utilizando as seguintes categorias:

@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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top