29

小さな画像をたくさん表示するフルスクリーンのtableViewを備えたこのアプリがあります。これらの画像は Web から取得され、バックグラウンド スレッドで処理され、次のような方法でディスクに保存されます。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
    // code that adds some glosses, shadows, etc 
    UIImage *output = UIGraphicsGetImageFromCurrentImageContext();

    NSData* cacheData = UIImagePNGRepresentation(output);
    [cacheData writeToFile:thumbPath atomically:YES];

    dispatch_async(dispatch_get_main_queue(), ^{
        self.image = output; // refreshes the cell using KVO
    });
});

このコードは、セルが最初に表示されたときにのみ実行されます (その後、画像は既にディスク上にあるため)。その場合、セルは以下を使用してロードされます。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    UIImage *savedImage = [UIImage imageWithContentsOfFile:thumbPath];

    if(savedImage) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.image = savedImage; // refreshes the cell using KVO
        });
    }
});

私の問題は、最初のケースでは、スクロールがバタースムーズであることです。しかし、2 番目のケース (ディスクから直接画像を読み取る場合) では、画像が読み込まれた後でも、スクロールが非常にぎくしゃくします。描画が遅れの原因です。Instruments を使用すると、 が CPU のほとんどを占有していることがわかりますcopyImageBlockSetPNG(に割り当てているときはそうではありませpng_read_nowん) 。inflateself.imageUIGraphicsGetImageFromCurrentImageContext()

最初のケースではUIImageが描画の生の出力であるのに対し、2番目のケースではPNGを描画するたびに解凍する必要があるため、これが発生すると想定しています。PNG の代わりに JPG を使用してみましたが、同様の結果が得られました。

これを修正する方法はありますか?たぶん、最初に描画されたときにのみPNGを解凍するようにするには?

4

3 に答える 3

24

問題は、+imageWithContentsOfFile:キャッシュされて遅延読み込みされることです。このようなことをしたい場合は、代わりにバックグラウンドキューで次のコードを使用してください。

// Assuming ARC
NSData* imageFileData = [[NSData alloc] initWithContentsOfFile:thumbPath];
UIImage* savedImage = [[UIImage alloc] initWithData:imageFileData];

// Dispatch back to main queue and set image...

さて、このコードでは、画像データの実際の解凍はまだ怠惰で少しコストがかかりますが、コード例の遅延読み込みで得られるファイルアクセスヒットほどではありません。

まだパフォーマンスの問題が発生しているため、UIImageにバックグラウンドスレッドの画像を強制的に解凍させることもできます。

// Still on background, before dispatching to main
UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // this isn't that important since you just want UIImage to decompress the image data before switching back to main thread
[savedImage drawAtPoint:CGPointZero];
UIGraphicsEndImageContext();

// dispatch back to main thread...
于 2012-04-13T22:41:30.777 に答える
16

画像を事前に描画して解凍する方法に関する Jason のヒントが重要ですが、画像全体をコピーして元の画像を破棄すると、パフォーマンスがさらに向上します。

iOS で実行時に作成された画像は、強制的に解凍した後でも、ファイルから読み込まれた画像よりも描画用に最適化されているようです。そのため、次のようにロードする必要があります (再ロードし続ける必要がないように、解凍したイメージを NSCache に入れることもお勧めします)。

- (void)loadImageWithPath:(NSString *)path block:(void(^)(UIImage *image))block
{
    static NSCache *cache = nil;
    if (!cache)
    {
        cache = [[NSCache alloc] init];
    }

    //check cache first
    UIImage *image = [cache objectForKey:path];
    if (image)
    {
        block(image);
        return;
    }

    //switch to background thread
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{

        //load image
        UIImage *image = [UIImage imageWithContentsOfFile:path];

        //redraw image using device context
        UIGraphicsBeginImageContextWithOptions(image.size, YES, 0);
        [image drawAtPoint:CGPointZero];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        //back to main thread
        dispatch_async(dispatch_get_main_queue(), ^{

            //cache the image
            [cache setObject:image forKey:path];

            //return the image
            block(image);
        });
    });
}
于 2013-05-31T18:35:23.350 に答える
6

別の方法による画像の解凍:

 NS_INLINE void forceImageDecompression(UIImage *image)
 {
  CGImageRef imageRef = [image CGImage];
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  CGContextRef context = CGBitmapContextCreate(NULL, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, CGImageGetWidth(imageRef) * 4, colorSpace,kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
  CGColorSpaceRelease(colorSpace);
  if (!context) { NSLog(@"Could not create context for image decompression"); return; }
  CGContextDrawImage(context, (CGRect){{0.0f, 0.0f}, {CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}}, imageRef);
  CFRelease(context);
}

使用:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  UIImage *image = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%u.jpg", pageIndex]];
  forceImageDecompression(image);

  dispatch_async(dispatch_get_main_queue(), ^{ 
    [((UIImageView*)page)setImage:image];
  });
}
于 2012-11-13T14:16:54.357 に答える