Question

Mon programme affiche une surface de défilement horizontale en mosaïque avec UIImageViews de gauche à droite. Le code s'exécute sur le thread d'interface utilisateur pour garantir que UIImageViews récemment visible se voit attribuer un UIImage fraîchement chargé. Le chargement se fait sur un fil de fond.

Tout fonctionne presque bien, sauf qu'il y a un bégaiement lorsque chaque image devient visible. Au début, je pensais que mon agent d’origine était en train de verrouiller quelque chose dans le fil de l’interface utilisateur. J'ai passé beaucoup de temps à le regarder et je me suis finalement rendu compte que UIImage effectuait des traitements paresseux supplémentaires sur le fil de l'interface utilisateur lorsqu'il devenait visible. Cela me laisse perplexe, car mon thread de travail possède un code explicite pour décompresser les données JPEG.

Quoi qu'il en soit, sur un pressentiment, j’ai écrit du code à rendre dans un contexte graphique temporaire sur le fil d’arrière-plan et, bien sûr, le bégaiement a disparu. UIImage est maintenant préchargé sur mon thread de travail. Jusqu'ici tout va bien.

Le problème est que mon nouveau "forcer le chargement d'image paresseux". méthode n'est pas fiable. Cela provoque EXC_BAD_ACCESS par intermittence. Je n'ai aucune idée de ce que UIImage fait en coulisse. Peut-être est-il en train de décompresser les données JPEG. Quoi qu’il en soit, la méthode est la suivante:

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

Et EXC_BAD_ACCESS se produit sur la ligne CGContextDrawImage. QUESTION 1: Est-ce que je suis autorisé à faire ceci sur un thread autre que le thread d'interface utilisateur? QUESTION 2: Qu'est-ce que UIImage est en réalité le "préchargement"? QUESTION 3: Quel est le moyen officiel de résoudre ce problème?

Merci d'avoir lu tout cela, tout conseil serait grandement apprécié!

Était-ce utile?

La solution

Les méthodes UIGraphics * sont conçues pour être appelées à partir du thread principal uniquement. Ils sont probablement la source de vos problèmes.

Vous pouvez remplacer UIGraphicsBeginImageContext () par un appel à CGBitmapContextCreate () ; c'est un peu plus compliqué (vous devez créer un espace colorimétrique, déterminer le tampon de la bonne taille à créer et l'allouer vous-même). Les méthodes CG * conviennent bien pour être exécutées à partir d'un autre thread.

Je ne sais pas comment vous initialisez UIImage, mais si vous le faites avec imageNamed: ou initWithFile: , vous pourrez peut-être le forcer. pour charger en chargeant vous-même les données puis en appelant initWithData: . Le bégaiement est probablement dû à des E / S de fichier paresseux, donc son initialisation avec un objet de données ne lui donnera pas la possibilité de lire à partir d'un fichier.

Autres conseils

J'ai eu le même problème de bégaiement, avec l'aide de moi, j'ai trouvé la solution appropriée ici: Chargement d'images non paresseuses dans iOS

Deux choses importantes à mentionner:

  • N'utilisez pas de méthodes UIKit dans un thread de travail. Utilisez CoreGraphics à la place.
  • Même si vous avez un fil d’arrière-plan pour le chargement et la décompression des images, vous aurez quand même un petit bégaiement si vous utilisez un masque de masque incorrect pour votre CGBitmapContext. Ce sont les options que vous devez choisir (je ne comprends toujours pas pourquoi):

-

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

J'ai posté un exemple de projet ici: SwapTest . à peu près la même performance que l'application Apples 'Photos pour le chargement / l'affichage d'images.

J'ai utilisé la catégorie SwapTest UIImage de @ jasamer pour forcer le chargement de mon grand UIImage (environ 3000x2100 px) dans un thread de travail (avec NSOperationQueue). Cela réduit le temps de bégaiement lorsque vous définissez l'image dans UIImageView à une valeur acceptable (environ 0,5 seconde sur iPad1).

Voici la catégorie SwapTest UIImage ... merci encore @jasamer:)

Fichier UIImage + ImmediateLoading.h

@interface UIImage (UIImage_ImmediateLoading)

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

@end

Fichier 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

Et voici comment créer NSOpeationQueue et définir l'image sur le fil 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];
}

J'ai eu le même problème, même si j'ai initialisé l'image en utilisant des données. (Je suppose que les données sont également chargées paresseusement?) J'ai réussi à forcer le décodage en utilisant la catégorie suivante:

@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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top