12

私のプログラムは、左から右に UIImageViews で並べられた水平スクロール サーフェスを表示します。コードは UI スレッドで実行され、新しく表示された UIImageView に新たにロードされた UIImage が割り当てられるようにします。読み込みはバックグラウンド スレッドで行われます。

各画像が表示されるときに途切れがあることを除いて、すべてがほぼ正常に機能します。最初は、バックグラウンド ワーカーが UI スレッドで何かをロックしていると思いました。私はそれを見て多くの時間を費やしましたが、最終的には、UIImage が最初に表示されたときに、UI スレッドで余分な遅延処理を行っていることに気付きました。私のワーカー スレッドには 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: UI スレッド以外のスレッドでこれを行うことはできますか? 質問 2: UIImage が実際に「プリロード」しているのは何ですか? 質問 3: この問題を解決する公式の方法は何ですか?

すべてをお読みいただきありがとうございます。アドバイスをいただければ幸いです。

4

4 に答える 4

22

私は同じ吃音の問題を抱えていましたが、いくつかの助けを借りて、ここで適切な解決策を見つけました: iOS での遅延のない画像の読み込み

言及すべき2つの重要なこと:

  • ワーカー スレッドで UIKit メソッドを使用しないでください。代わりに CoreGraphics を使用してください。
  • 画像を読み込んで圧縮解除するためのバックグラウンド スレッドがある場合でも、CGBitmapContext に間違ったビットマスクを使用すると、少し途切れることがあります。これはあなたが選択しなければならないオプションです(理由はまだ少しわかりません):

-

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

ここにサンプル プロジェクトを投稿しました: SwapTest、画像の読み込み/表示に関して、Apple の写真アプリとほぼ同じパフォーマンスを発揮します。

于 2011-03-11T02:17:32.667 に答える
11

@jasamer の SwapTest UIImage カテゴリを使用して、大きな UIImage (約 3000x2100 ピクセル) をワーカー スレッド (NSOperationQueue を使用) に強制的にロードしました。これにより、イメージを UIImageView に設定する際のスタッター時間が許容値 (iPad1 では約 0.5 秒) に短縮されます。

ここに 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];
}
于 2011-06-15T08:39:31.097 に答える
7

メソッドは、UIGraphics*メイン スレッドからのみ呼び出されるように設計されています。それらはおそらくあなたの問題の原因です。

UIGraphicsBeginImageContext()への呼び出しに置き換えることができCGBitmapContextCreate()ます。もう少し複雑です (色空間を作成し、作成する適切なサイズのバッファーを見つけて、自分で割り当てる必要があります)。メソッドは別のCG*スレッドから実行しても問題ありません。


UIImage をどのように初期化しているかはわかりませんが、imageNamed:またはで実行している場合initWithFile:は、自分でデータを読み込んでinitWithData:. スタッターはおそらく遅延ファイル I/O が原因であるため、データ オブジェクトで初期化しても、ファイルから読み取るオプションが与えられません。

于 2009-12-30T20:15:03.763 に答える
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
于 2010-09-09T05:47:51.187 に答える