web-dev-qa-db-ja.com

UIImage imageNamedのディスペル:FUD

2014年2月編集:この質問はiOS 2.0からのものであることに注意してください!それ以降、画像の要件と処理は大きく変わっています。 Retinaは画像をより大きくし、画像の読み込みを少し複雑にします。 iPadとRetina画像の組み込みサポートにより、コードでImageNamedを使用する必要があります

多くの人がimageNamedは悪いと言っていますが、同じ数の人がパフォーマンスが良いと言っています-特にUITableViewsをレンダリングするとき。 this SO question たとえば)または this article iPhoneDeveloperTips.comを参照してください

UIImageimageNamedメソッドはリークに使用されていたため、回避するのが最善でしたが、最近のリリースで修正されました。システムを信頼して画像をキャッシュできる場所と、余分な距離を移動して自分で行う必要がある場所について、合理的な判断を下すために、キャッシュアルゴリズムをよりよく理解したいと思います。私の現在の基本的な理解は、ファイル名で参照されるNSMutableDictionaryの単純なUIImagesであるということです。それは大きくなり、メモリがなくなると、ずっと小さくなります。

たとえば、imageNamedの背後にある画像キャッシュがdidReceiveMemoryWarningに応答しないことを誰もが確実に知っていますか? Appleがこれを行わない可能性は低いようです。

キャッシュアルゴリズムについての洞察がある場合は、ここに投稿してください。

117
Rog

tldr:ImagedNamedは問題ありません。メモリを適切に処理します。それを使用して心配しないでください。

2012年11月編集:この質問はiOS 2.0のものです!それ以降、画像の要件と処理は大きく変わりました。 Retinaは画像をより大きくし、画像の読み込みを少し複雑にします。 iPadおよびRetina画像の組み込みサポートにより、コードでImageNamedを使用する必要があります。さて、後世のために:

Apple Devフォーラムの 姉妹スレッド は、より良いトラフィックを受け取りました。具体的には Rincewind は権限を追加しました。

IPhone OS 2.xには、メモリ警告の後でもimageNamed:キャッシュがクリアされないという問題があります。同時に、+ imageNamed:はキャッシュではなく、利便性のために多くの用途に使用されています。これにより、問題が本来よりも大きくなる可能性があります。

警告しながら

スピード面では、何が起こっているのかという一般的な誤解があります。 + imageNamed:が行う最大のことは、ソースファイルから画像データをデコードすることです。これにより、ほとんどの場合、データサイズが大幅に増大します(たとえば、画面サイズのPNGファイルは、圧縮すると数十KBを消費しますが、半分以上のMBを消費します解凍-幅*高さ* 4)。対照的に、+ imageWithContentsOfFile:は、画像データが必要になるたびにその画像を解凍します。想像できるように、画像データが1回だけ必要な場合は、キャッシュされたバージョンの画像が必要以上に長く存在することを除いて、ここでは何も勝ちません。ただし、頻繁に再描画する必要がある大きな画像がある場合は、代替手段がありますが、主に推奨するのはその大きな画像の再描画を避けることです:)。

キャッシュの一般的な動作に関しては、ファイル名に基づいてキャッシュします(したがって、同じ名前の+ imageNamedの2つのインスタンスは、同じキャッシュデータへの参照になります)。 + imageNamed:。 iPhone OS 2.xでは、メモリの警告を受け取ったときに、バグによりキャッシュが縮小されません。

そして

私の理解では、+ imageNamed:キャッシュはiPhone OS 3.0のメモリ警告を尊重する必要があるということです。機会があればテストして、そうでない場合はバグを報告してください。

だから、あなたはそれを持っています。 imageNamed:窓を壊したり、子供を殺したりしません。とてもシンプルですが、最適化ツールです。悲しいことに、それはひどく名前が付けられており、使いやすい同等の値はありません-したがって、人々はそれを使いすぎて、単に仕事をするときに動揺します

それを修正するためにUIImageにカテゴリを追加しました:

// header omitted
// Before you waste time editing this, please remember that a semi colon at the end of a method definition is valid and a matter of style.
+ (UIImage*)imageFromMainBundleFile:(NSString*)aFileName; {
    NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", bundlePath,aFileName]];
}

Rincewindには、独自の最適化バージョンを構築するためのサンプルコードも含まれています。メンテナンスの価値があるとは思えませんが、ここでは完全を期しています。

CGImageRef originalImage = uiImage.CGImage;
CFDataRef imageData = CGDataProviderCopyData(
     CGImageGetDataProvider(originalImage));
CGDataProviderRef imageDataProvider = CGDataProviderCreateWithCFData(imageData);
CFRelease(imageData);
CGImageRef image = CGImageCreate(
     CGImageGetWidth(originalImage),
     CGImageGetHeight(originalImage),
     CGImageGetBitsPerComponent(originalImage),
     CGImageGetBitsPerPixel(originalImage),
     CGImageGetBytesPerRow(originalImage),
     CGImageGetColorSpace(originalImage),
     CGImageGetBitmapInfo(originalImage),
     imageDataProvider,
     CGImageGetDecode(originalImage),
     CGImageGetShouldInterpolate(originalImage),
     CGImageGetRenderingIntent(originalImage));
CGDataProviderRelease(imageDataProvider);
UIImage *decompressedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);

このコードとのトレードオフは、デコードされた画像はより多くのメモリを使用しますが、レンダリングは高速になることです。

86
Rog

私の経験では、imageNamedによって作成された画像キャッシュはメモリ警告に応答しません。 mem管理の範囲内でできる限り無駄のない2つのアプリケーションがありましたが、memが不足しているために不可解にクラッシュしていました。 imageNamedを使用して画像を読み込むのをやめると、両方のアプリケーションが劇的に安定しました。

どちらのアプリケーションもやや大きな画像をロードしたことは認めますが、まったく普通のものではありません。最初のアプリケーションでは、ユーザーが2回同じ画像に戻る可能性は低いため、キャッシュを完全にスキップしました。 2つ目は、UISMをNSMutableDictionaryに保持し、メモリ警告を受け取った場合にその内容をフラッシュするという、まさにあなたが言及したことを行う非常に単純なキャッシュクラスを構築しました。 imageNamed:がそのようにキャッシュされる場合、パフォーマンスのアップグレードは見られなかったはずです。これはすべて2.2で実行されていました-これに3.0の影響があるかどうかはわかりません。

この問題に関する私の他の質問は、最初のアプリからここにあります: IImageキャッシュに関するStackOverflowの質問

もう1つの注意点-InterfaceBuilderは、カバーの下にあるimageNamedを使用します。この問題に遭遇した場合に留意すべきこと。

5
Bdebeez