UIImage imageNamed:FUDを払拭する


117

2014年2月の編集:この質問はiOS 2.0のものです。それ以来、画像の要件と処理は大きく変わりました。Retinaを使用すると、画像が大きくなり、読み込みが少し複雑になります。iPadおよびRetinaイメージの組み込みサポートにより、コードでImageNamedを使用する必要があります

多くの人imageNamedが悪いと言っていますが、特にUITableViewsをレンダリングする場合は、パフォーマンスが良いと言っている人の数と同じです。参照してください。このSOの質問例や、この記事 iPhoneDeveloperTips.com上を

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

たとえば、背後にある画像キャッシュimageNamedが応答しないことを誰かが知っていdidReceiveMemoryWarningますか?Appleがこれをしないとは思えない。

キャッシングアルゴリズムに関する洞察がある場合は、こちらに投稿してください。


2
私も。SOは私が思ったほど天才に満ちていないようです。
Rog、

これを調査するのに時間を費やしたようです。最新のココアタッチバージョンでUIImage imageNamedの悪影響を示す実験を行いましたか?UITableViewを作成し、多数(数千)の行を追加して、行ごとに異なる画像を表示したときにパフォーマンスが低下するかどうかを確認します。多分それから人々はあなたの調査結果にコメントすることができます。
stefanB 2009年

実際、私はそれほど多くの実験を行っていません。私はimageNamedを使用していますが、まだこのアプリをメモリ最適化していませんが、imageNamedをメモリの独り占めとして直接指すことはできません。ここでimagedNamedについて不満を述べているブログ投稿をいくつか指摘しましたが、アップルボードで同様の質問をして、より多くのアクションを起こしました。時間があれば、ここに要約とリンクを再投稿し、返信が届いたと感じたら、ここにリンクします。
Rog

回答:


85

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

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

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

iPhone OS 2.xには、メモリ警告の後でさえ、imageNamed:キャッシュがクリアされない問題があります。同時に、+ imageNamed:はキャッシュではなく、利便性のために多くの利用を得ています。これはおそらく、問題を本来よりもさらに拡大しているでしょう。

警告しながら

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

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

そして

+ 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には、独自の最適化バージョンを構築するためのサンプルコードも含まれています。私はそれがmaintentaceの価値があるとは思いませんが、ここでそれは完全性のためです。

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);

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


iOS11では、画像がxcassetカタログから取得されている場合、[UIImage imageNamed:]が画像をキャッシュから削除しないようです。これは、メモリ不足によるクラッシュにつながります。
Juraj Antas

5

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

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

この問題に関する他の質問は、私の最初のアプリの UIImageキャッシュに関するStackOverflowの質問で確認できます。

もう1つの注意事項-InterfaceBuilderは内部でimageNamedを使用しています。この問題が発生した場合に覚えておくべきこと。


私はまったく同じ動作を確認し、ファイルから読み取ることで直接解決しました。また、非常に大きな画像(11,456 x 3,226 px、15MB)を読み込もうとしたときにも発生します。私の理論は、システムがImageNamedキャッシュ操作の途中でメモリ不足になるというものです。そして、この場合に組み込まれた処理はありません。
Dogweather 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.