2011-03-10 17 views
39

私はバックグラウンドスレッドでUIImagesを読み込み、iPadに表示しようとしています。しかし、画像にimageViewsのビュープロパティを設定すると、スタッターがあります。 私はすぐにその画像の読み込みがiOSの上で怠惰で考え出し、この問題の部分的な解決策を見つけた:iOSでの非レイジー画像の読み込み

CGImage/UIImage lazily loading on UI thread causes stutter

これは実際のスレッドでロードする画像を強制的に、しかし表示する際にスタッターがまだあります画像。

ここで私のサンプルプロジェクトを見つけることができます:http://www.jasamer.com/files/SwapTest.zip(編集:fixed version)、SwapTestViewControllerをチェックしてください。画像をドラッグしてスタッターを見てみてください。

私は吃音は、この(のforceload方法は、私は上記の投稿スタックオーバーフローの質問から取られたもの)であることを作成したテストコード:私は吃音ができる知っている理由は2つあります

NSArray* imagePaths = [NSArray arrayWithObjects: 
         [[NSBundle mainBundle] pathForResource: @"a.png" ofType: nil], 
         [[NSBundle mainBundle] pathForResource: @"b.png" ofType: nil], nil]; 

NSOperationQueue* queue = [[NSOperationQueue alloc] init]; 

[queue addOperationWithBlock: ^(void) { 
    int imageIndex = 0; 
    while (true) { 
     UIImage* image = [[UIImage alloc] initWithContentsOfFile: [imagePaths objectAtIndex: imageIndex]]; 
     imageIndex = (imageIndex+1)%2; 
     [image forceLoad]; 

     //What's missing here? 

     [self performSelectorOnMainThread: @selector(setImage:) withObject: image waitUntilDone: YES]; 
     [image release]; 
    } 
}]; 

回避:

(1)Appleは写真アプリに吃音せずに画像をロードすることができます

(2)PLACEHOLDER1とPLACEHOLDER2後に吃音が発生することはありません。このコードは、上記のコードのこの修正版では一度表示されています:

ただし、すべての画像をメモリに保存できません。

これは、forceLoadが完全なジョブを実行しないことを意味します。イメージが実際に表示される前に何か他のことが起こっています。 それは誰が知っていますか、どのようにバックグラウンドスレッドに入れることができますか? Tommysのヒントのいくつかを使用し

おかげで、ジュリアン

更新

。 CGSConvertBGRA8888toRGBA8888は非常に時間がかかるので、遅れの原因となっている色変換のようです。 そのメソッドの(反転した)呼び出しスタックを次に示します。

Running  Symbol Name 
6609.0ms  CGSConvertBGRA8888toRGBA8888 
6609.0ms   ripl_Mark 
6609.0ms   ripl_BltImage 
6609.0ms   RIPLayerBltImage 
6609.0ms   ripc_RenderImage 
6609.0ms    ripc_DrawImage 
6609.0ms    CGContextDelegateDrawImage 
6609.0ms    CGContextDrawImage 
6609.0ms    CA::Render::create_image_by_rendering(CGImage*, CGColorSpace*, bool) 
6609.0ms     CA::Render::create_image(CGImage*, CGColorSpace*, bool) 
6609.0ms     CA::Render::copy_image(CGImage*, CGColorSpace*, bool) 
6609.0ms     CA::Render::prepare_image(CGImage*, CGColorSpace*, bool) 
6609.0ms     CALayerPrepareCommit_(CALayer*, CA::Transaction*) 
6609.0ms      CALayerPrepareCommit_(CALayer*, CA::Transaction*) 
6609.0ms      CALayerPrepareCommit_(CALayer*, CA::Transaction*) 
6609.0ms      CALayerPrepareCommit_(CALayer*, CA::Transaction*) 
6609.0ms      CALayerPrepareCommit 
6609.0ms       CA::Context::commit_transaction(CA::Transaction*) 
6609.0ms       CA::Transaction::commit() 
6609.0ms       CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) 
6609.0ms       __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ 
6609.0ms        __CFRunLoopDoObservers 
6609.0ms        __CFRunLoopRun 
6609.0ms        CFRunLoopRunSpecific 
6609.0ms        CFRunLoopRunInMode 
6609.0ms         GSEventRunModal 
6609.0ms         GSEventRun 
6609.0ms         -[UIApplication _run] 
6609.0ms         UIApplicationMain 
6609.0ms          main   

彼が提案した最後のビットマスクの変更は、悲しいことに何も変わっていません。

+0

ありがとうございます。この問題を解決しようとしている約1000個のクロムウィンドウを開きましたが、誰も何の考えもしていませんでした。 – Sheepdogsheep

+0

さらに、 'NSBundle'はスレッドセーフではありません。 – SK9

答えて

39

メインスレッドのみでUIKitを使用できます。したがって、メインスレッド以外のスレッドからUIImageを使用するため、コードは技術的に無効です。 CoreGraphicsを単独で使用して、バックグラウンドスレッドでグラフィックスをロード(および非遅延描画)し、メインスレッドにCGImageRefをポストし、そこでUIImageに変換する必要があります。現在の実装では動作しないように見えるかもしれませんが、保証されません。この地域では迷信と悪い練習がたくさんあるようですので、悪いアドバイスを見つけるのは驚くことではありません。バックグラウンドスレッドで実行することをお勧めし

// get a data provider referencing the relevant file 
CGDataProviderRef dataProvider = CGDataProviderCreateWithFilename(filename); 

// use the data provider to get a CGImage; release the data provider 
CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, 
                kCGRenderingIntentDefault); 
CGDataProviderRelease(dataProvider); 

// make a bitmap context of a suitable size to draw to, forcing decode 
size_t width = CGImageGetWidth(image); 
size_t height = CGImageGetHeight(image); 
unsigned char *imageBuffer = (unsigned char *)malloc(width*height*4); 

CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); 

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

CGColorSpaceRelease(colourSpace); 

// draw the image to the context, release it 
CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), image); 
CGImageRelease(image); 

// now get an image ref from the context 
CGImageRef outputImage = CGBitmapContextCreateImage(imageContext); 

// post that off to the main thread, where you might do something like 
// [UIImage imageWithCGImage:outputImage] 
[self performSelectorOnMainThread:@selector(haveThisImage:) 
     withObject:[NSValue valueWithPointer:outputImage] waitUntilDone:YES]; 

// clean up 
CGImageRelease(outputImage); 
CGContextRelease(imageContext); 
free(imageBuffer); 

のmalloc /無料を行う必要は、あなたがiOS 4以降またはにしている場合は、あなただけCGBitmapContextCreateの関連するパラメータとしてNULLを渡すことができますありませんCoreGraphicsが独自のストレージを整理するようにします。

それので、これはあなたがに投稿するソリューションとは異なります

  1. はPNGデータソースからCGImageを作成します - 遅延ロードが適用されるので、これが完全にロードし、解凍された画像
  2. が作成する必要はないですPNGと同じサイズのビットマップコンテキスト
  3. は、PNGデータソースからビットマップコンテキストにCGImageを描画します。実際のカラー値はCからアクセス可能な場所に配置する必要があるため、完全なロードと復元が必要ですアレイ。このステップはあなたがリンクするforceLoadが行く限りです。
  4. は、おそらくUIImageになるためにメインスレッドを切った画像に
  5. 記事そのイメージをビットマップコンテキストを変換

ので、ロードの事と表示されたものとの間にオブジェクトのない連続性はありません。ピクセルデータはC配列を通るので(隠された偽陰謀の機会はありません)、配列に正しく入れられた場合にのみ、最終的な画像を作ることができます。

+1

その情報をありがとう。 UIImageはとても便利です;-)私のメソッドをあなたのコードに置き換えましたが、まだまだそこにはありません。 – jasamer

+0

imageView.layer.contents = theCGImage;で画像を設定しようとしましたが、少し改善されているようですが、十分ではありません。画像データをグラフィックスメモリにコピーするのにかかる時間、または画像表現について何かを最初に変更しなければならないので、スタッタが発生する可能性はありますか? – jasamer

+0

それは間違いなく画像のロード/デコードのコストではありません。上記のコードをロード/デコードすることは不可能です。いくつかのNSTimeInterval time = [NSDate timeIntervalSinceReferenceDate]を追加してみましたか?違いを記録しましたか?それは実際にどのくらいの時間がかかるのかを教えてくれるので、本当の原因は何かを理解することができます。 – Tommy

14

[OK]を、それを理解した - トミーの助けを借りて多くの。ありがとうございました!

あなたは

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

を使用してコンテキストを作成する場合は、メイン実行ループは、任意のより多くのメインスレッド上の任意の変換が発生することはありません。 表示がバーティーになりました。 (?フラグは少し直観に反している、あなたはkCGImageAlphaPremultipliedFirstを選択する必要があり、なぜ誰もが知っているん)

編集:http://www.jasamer.com/files/SwapTest-Fixed.zip

が固定サンプルプロジェクトをアップロード。 画像のパフォーマンスに問題がある場合は、これが最適な出発点です!

+0

インターネットから大きな画像をダウンロードした場合、どうすればいいですか? - (UIImage *)initImmediateLoadWithContentsOfFile:(NSString *)path;ローカルからのロードです – pengwang

10

UIImage+ImmediateLoad.m

すぐに画像をデコードします。 UIImage -initWithContentsOfFile:、-imageWithCGImage:およびCG *は、iOS4の後ではスレッドセーフです。

+0

これらのメソッドは現在スレッドセーフです。 UIImageのようなコードでは見た目がよくなり、私はサンプルプロジェクトを更新します。それは、しかし、吃音を取り除くために調整されたビットマスクオプションが必要でした。 – jasamer

+0

私は実際にコードを使ってイメージを読み込んでイメージをロードして放棄した後、いつものように自分のビジネスに行き着くだけの利点があります。メソッドの名前を 'imageImmediateLoadWithName'に変更しました。' [UIImage imageImmediateLoadWithName:@ "myImage.png"]; 'イメージへの後続の参照をはるかに高速にするには十分です。 –

+0

「UIImage以外の-initWithContentsOfFile :, -imageWithCGImage:およびCG *はiOS4以降のスレッドセーフです」という参照はありますか? UIImageのドキュメントやiOSの変更ログに影響するものは何もありません(https://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS4.html#//apple_ref/doc/uid/TP40009559)。 -SW1) – Tommy

関連する問題