2009-11-29 1 views
12

Моя программа отображает горизонтальную прокручиваемую поверхность, облицованную UIImageViews слева направо. Код работает в потоке пользовательского интерфейса, чтобы убедиться, что вновь видимые UIImageViews имеют только что загруженный UIImage, назначенный им. Погрузка происходит в фоновом потоке.CGImage/UIImage lazily loading on UI thread заставляет заикаться

Все работает почти нормально, за исключением того, что заикается, когда каждое изображение становится видимым. Сначала я подумал, что мой фоновый работник блокировал что-то в потоке пользовательского интерфейса. Я потратил много времени на это, и в конце концов понял, что UIImage делает некоторую дополнительную ленивую обработку в потоке пользовательского интерфейса, когда она становится видимой. Это меня озадачивает, так как мой рабочий поток имеет явный код для распаковки данных JPEG.

Во всяком случае, на догадку я написал код для визуализации во временный графический контекст на фоне потока и - конечно же, заикание ушло. UIImage теперь предварительно загружается в рабочий поток. Все идет нормально.

Проблема в том, что мой новый метод «сила ленивой загрузки изображения» ненадежен. Это вызывает прерывистый EXC_BAD_ACCESS. Я не знаю, что делает UIImage за кадром. Возможно, это распаковка данных JPEG. Во всяком случае, этот метод:

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

И EXC_BAD_ACCESS происходит на линии CGContextDrawImage. ВОПРОС 1: Могу ли я сделать это в потоке, отличном от потока пользовательского интерфейса? ВОПРОС 2: Что такое UIImage на самом деле «до загрузки»? ВОПРОС 3: Каков официальный способ решения этой проблемы?

Спасибо, что прочитали все это, любые советы были бы очень признательны!

ответ

7

Методы UIGraphics* предназначены для вызова только из основной нити. Вероятно, это источник вашей проблемы.

Вы можете заменить UIGraphicsBeginImageContext() позвоните по номеру CGBitmapContextCreate(); это немного более активно (вам нужно создать цветовое пространство, определить нужный буфер для создания и выделить его самостоятельно). Методы CG* отлично подходят для работы из другого потока.


Я не знаю, как вы инициализации UIImage, но если вы делаете это с imageNamed: или initWithFile:, то вы можете быть в состоянии заставить его загрузить с помощью загрузки данных самостоятельно и затем вызвать initWithData:. Заикание, вероятно, связано с ленивым вводом-выводом файлов, поэтому инициализация его объектом данных не даст ему возможности чтения из файла.

+0

привет, спасибо, что нашли время, чтобы помочь с этим. В конце концов я пришел к такому осознанию. Я могу только представить, какое злое небезопасное кэширование происходит внутри этих методов UI *. Мой интерфейс теперь заикается бесплатно ..... – JBx

+0

Я в замешательстве ... вы говорите imageNamed: вызывает ленивый файл IO, но .. создание объекта данных не будет? Или вы говорите, что у вас должны собираться объекты данных, где-то, чтобы они были извлечены, когда это необходимо? –

+1

Jasconius: Если вы создаете объект UIImage и передаете ему путь к файлу, он может не загружать все изображение в память, пока вы его не нарисуете. Если вы загружаете файл в память с помощью объекта NSData, передайте его в UIImage, у него нет выбора, кроме как сохранить его в памяти. Что лучше всего зависит от конкретной ситуации. – benzado

4

У меня была такая же проблема, хотя я инициализировал изображение, используя данные. (Я предполагаю, что данные загружаются лениво, тоже?) Мне удалось заставить декодирование с помощью следующей категории:

@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 
+0

Казалось, это сработало очень хорошо для меня. Благодаря!!! : D – Marky

22

я имел такую ​​же проблему заикания, с некоторой помощью я понял, правильное решение здесь : Non-lazy image loading in iOS

Две важные вещи, говоря:

  • не следует использовать методы UIKit в рабоче-нить. Вместо этого используйте CoreGraphics.
  • Даже если у вас есть фоновый поток для загрузки и распаковки изображений, вы все равно будете немного заикаться, если вы используете неправильную битовую маску для вашего CGBitmapContext.Это те опции, которые вы должны выбрать (это все еще немного неясно мне, почему):

-

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

Я отправил образец проекта здесь: SwapTest, он имеет примерно такой же Performace как приложение «Яблоки» для загрузки/отображения изображений.

+3

Я не могу понять, почему у вас было 0 upvotes. Ваше решение было единственным, что сработало для меня. Если бы я мог, я бы дал вам 10 яиц. СПАСИБО!!! – Kalle

11

Я использовал категорию swapTest UIImage @ jasamer для принудительной загрузки моего большого UIImage (около 3000x2100 px) в рабочий поток (с NSOperationQueue). Это уменьшает время заикания при установке изображения в UIImageView до приемлемого значения (около 0,5 секунды на iPad1).

Вот SwapTest UIImage категория ... еще раз спасибо :) @jasamer

UIImage + ImmediateLoading.h файл

@interface UIImage (UIImage_ImmediateLoading) 

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

@end 

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 

И это как я создаю NSOpeationQueue и устанавливаю изображение на главную резьбу ...

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

По-прежнему есть вызов UIImage в потоке, отличном от вашего основного потока, поэтому это решение небезопасно. –

+0

Вы можете использовать CGImageRef imageRef = CGImageCreateWithJPEGDataProvider (CGDataProviderCreateWithFilename ([путь UTF8String]), NULL, NO, kCGRenderingIntentDefault) вместо UIImage – Tieme

+0

@JorisMans как вызывать UIImage вне основного потока небезопасно? – Hlung