Frage

Ihr Programm zeigt eine horizontale Scrollen Oberfläche mit UIImageViews gefliest von links nach rechts. Code läuft auf dem UI-Thread, um sicherzustellen, dass neu sichtbar UIImageViews eine frisch geladene UIImage, die sie zugewiesen hat. Die Beladung erfolgt auf einem Hintergrund-Thread.

Alles funktioniert fast gut, außer es ein Stottern ist wie jedes Bild sichtbar wird. Zuerst dachte ich, mein Hintergrund Arbeiter etwas in dem UI-Thread wurde verriegelt. Ich verbrachte viel Zeit es zu betrachten und erkannte schließlich, dass die UIImage einige zusätzliche faul Verarbeitung auf dem UI-Thread tut, wenn es zuerst sichtbar wird. Das verwirrt mich, da mein Arbeitsthread zum Dekomprimieren JPEG-Daten explizit Code.

Wie auch immer, auf einer Ahnung ich einige Codes geschrieben in einen temporären Grafikkontext auf dem Hintergrund-Thread zu machen und - sicher genug, ging das Stottern weg. Die UIImage wird jetzt auf meinem Arbeitsthread vorbelastet. So weit so gut.

Das Problem ist, dass mein neuer „Kraft faul Last des Bildes“ Methode unzuverlässig ist. Es verursacht intermittierende EXC_BAD_ACCESS. Ich habe keine Ahnung, was UIImage ist eigentlich hinter den Kulissen zu tun. Vielleicht ist es die JPEG-Daten zu dekomprimieren. Wie dem auch sei, die Methode ist:

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

Und die EXC_BAD_ACCESS geschieht auf der CGContextDrawImage Linie. FRAGE 1: Bin ich dies auf einem anderen Thread als dem UI-Thread zu tun erlaubt? Frage 2: Was die UIImage ist eigentlich "pre-loading"? FRAGE 3: Was ist der offizielle Weg, um dieses Problem zu lösen

Vielen Dank für alles, was zu lesen, würde jede Beratung sehr geschätzt!

War es hilfreich?

Lösung

Die UIGraphics* Verfahren sind so ausgelegt, nur aus dem Haupt-Thread aufgerufen werden. Sie sind wahrscheinlich die Quelle Ihrer Probleme.

Sie können UIGraphicsBeginImageContext() mit einem Aufruf an CGBitmapContextCreate() ersetzen; es ist ein wenig mehr beteiligt (Sie benötigen einen Farbraum zu schaffen, herauszufinden, die richtige Größe Puffer aus zu erstellen und verteilen es sich). Die CG* Methoden sind in Ordnung aus einem anderen Thread ausgeführt werden.


Ich bin mir nicht sicher, wie Sie UIImage sind initialisiert, aber wenn man es mit imageNamed: oder initWithFile: tun dann könnten Sie in der Lage sein, sie zu zwingen, durch das Laden der Daten selbst zu laden und dann initWithData: aufrufen. Das Stottern ist wahrscheinlich auf faule Datei-I / O, so dass es mit einem Datenobjekt initialisiert wird ihm nicht die Möglichkeit geben, aus einer Datei zu lesen.

Andere Tipps

Ich habe das gleiche Problem Stottern hatte, mit etwas Hilfe, die ich hier die richtige Lösung herausgefunden: Nicht faul Bild Laden in iOS

Zwei wichtige Dinge zu erwähnen:

  • Sie nicht UIKit Methoden in einem Arbeiter-Thread verwenden. Verwenden Sie stattdessen Core Graphics.
  • Auch wenn Sie einen Hintergrund-Thread für das Laden und Dekomprimieren von Bildern haben, werden Sie noch ein wenig stottern, wenn Sie die falsche Bitmaske für Ihre CGBitmapContext verwenden. Dies sind die Optionen, die Sie (es ist immer noch ein wenig unklar mir warum) wählen:

-

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

Ich habe ein Beispielprojekt hier gepostet: SwapTest , hat es etwa die gleiche performace wie Apples Foto-App für das Laden / Anzeigen von Bildern.

Ich habe @ jasamer der SwapTest UIImage Kategorie zu zwingen, meine große UIImage (ca. 3000x2100 Pixel) in einem Arbeitsthread (mit NSOperationQueue) zu laden. Dies reduziert die Zeit Stottern, wenn das Bild in die UIImageView auf einen akzeptablen Wert (etwa 0,5 sec auf iPad1) einstellen.

Hier ist SwapTest UIImage Kategorie ... Nochmals vielen Dank @jasamer:)

UIImage + ImmediateLoading.h Datei

@interface UIImage (UIImage_ImmediateLoading)

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

@end

UIImage + ImmediateLoading.m Datei

#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

Und das ist, wie ich NSOpeationQueue erstellen und das Bild auf dem Haupt-Thread ...

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

Ich hatte das gleiche Problem, auch wenn ich das Bild mit Daten initialisiert. (Ich denke, die Daten lazily geladen werden, auch?) Ich gelungen Decodierung zu zwingen, die folgende Kategorie mit:

@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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top