15

imageNamed:私たちは皆、UIImage のメソッドの謎めいた舞台裏のキャッシュ メカニズムについて知っています。Apple のUIImage Class Referenceには、次のように書かれています。

メモリ不足の状況では、画像データを UIImage オブジェクトから削除して、システムのメモリを解放することができます。このパージ動作は、オブジェクト自体ではなく、UIImage オブジェクトによって内部的に保存された画像データのみに影響します。データが消去されたイメージを描画しようとすると、イメージ オブジェクトは元のファイルからデータを自動的に再読み込みします。ただし、この余分な負荷ステップにより、パフォーマンスがわずかに低下する可能性があります。

ただし、実際には、ドキュメントが示唆するように、画像データは「システムのメモリを解放するために UIImage オブジェクトから削除される」ことはありません。代わりに、アプリは「メモリ不足のために」終了するまでメモリ警告を受け取ります。
編集: Xcode プロジェクトで従来のイメージ ファイル参照を使用する場合、UIImage キャッシュは正常に機能します。メモリが解放されないのは、アセット カタログに移行するときだけです。

画像の長いリストをスクロールするために、いくつかの UIImageView を使用して UIScrollView を実装しました。スクロールすると、次の画像が読み込まれ、UIImageView のimageプロパティに割り当てられ、以前に保持されていた UIImage への強力なリンクが削除されます。

imageNamed:キャッシュ メカニズムにより、すぐにメモリが不足し、約 170 MB のメモリが割り当てられた状態でアプリが終了します。

もちろんimageNamed:、カテゴリ内のクラス メソッドをオーバーライドするなど、カスタム キャッシュ メカニズムを実装するための興味深いソリューションがたくさんあります。imageWithContentOfFile:多くの場合、WWDC 2011 で Apple 開発者によって提案されたように、画像データをキャッシュしないクラス メソッドが代わりに使用されます。

これらのソリューションは、通常の画像ファイルでは問題なく機能しますが、パスとファイル拡張子を取得する必要がありますが、これは私が望むほどエレガントではありません。

ただし、Xcode 5 で導入された新しいアセット カタログを使用して、デバイスに応じて画像を条件付きで読み込むメカニズムと効率的な画像ファイル ストレージを利用しています。imageNamed:今のところ、明らかな解決策がない限り、を使用せずにアセット カタログから画像をロードする簡単な方法はないようです。

アセット カタログを使用した UIImage キャッシング メカニズムは理解できましたか?

次のような UIImage のカテゴリを実装したいと思います。

static NSCache *_cache = nil;

@implementation UIImage (Caching)

+ (UIImage *)cachedImageNamed:(NSString *)name {
    if (!_cache) _cache = [[NSCache alloc] init];

    if (![_cache objectForKey:name]) {
        UIImage *image = ???; // load image from Asset Catalog without internal caching mechanism
        [_cache setObject:image forKey:name];
    }

    return [_cache objectForKey:name];
}

+ (void)emptyCache {
    [_cache removeAllObjects];
}

@end

もちろん、アセット カタログを使用する場合のドキュメントに記載されているように、 の内部キャッシュをより詳細に制御しUIImage、メモリ不足の状態で画像データをパージする可能性がある方法はさらに優れています。

読んでくれてありがとう。あなたのアイデアを楽しみにしています!

4

1 に答える 1

4

更新:キャッシュの削除は正常に機能します (少なくとも iOS 8.3 以降)。

私は同じ問題(iOS 7.1.1)に遭遇していますが、@Lukasが正しいかもしれません

間違いが Apple の ... キャッシング内ではなく、あなたの .. コード内にある可能性が高いです。

したがって、私はまだ問題が発生している非常に単純なテスト アプリ (以下の完全なソースを表示) を作成しました。何か問題がある場合は、私に知らせてください。私はそれが本当に画像サイズに依存することを知っています。iPad Retina でのみ問題が発生します。

  @interface ViewController ()

  @property (nonatomic, strong) UIImageView *imageView;
  @property (nonatomic, strong) NSArray *imageArray;
  @property (nonatomic) NSUInteger   counter;

  @end

  @implementation ViewController

  - (void)viewDidLoad
  {
      [super viewDidLoad];

      self.imageArray = @[@"img1", ...  , @"img568"];
      self.counter = 0;

      UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
      self.imageView = [[UIImageView alloc] initWithImage: image];
      [self.view addSubview: self.imageView];

      [self performSelector:@selector(loadNextImage) withObject:nil afterDelay:1];
  }

  - (void)didReceiveMemoryWarning
  {
      [super didReceiveMemoryWarning];
      NSLog(@"WARN: %s", __PRETTY_FUNCTION__);
  }


  - (void)loadNextImage{

      self.counter++;
      if (self.counter < [self.imageArray count])
      {
          NSLog(@"INFO: %s - %lu - %@",
                __PRETTY_FUNCTION__,
                (unsigned long)self.counter,
                [self.imageArray objectAtIndex:self.counter]);
          UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
          self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
          [self.imageView setImage:image];

          [self performSelector:@selector(loadNextImage) withObject:nil afterDelay:0.2];
      } else
      {
          NSLog(@"INFO: %s %@", __PRETTY_FUNCTION__, @"finished");
          [self.imageView removeFromSuperview];
      }
  }
  @end

インプレース実装

画像アセットを保持するコードを書きましたが、imageWithData:orでロードしますimageWithContentsOfFile:メモリの問題を防ぐために imageNamed なしで xcassets を使用しますか?

于 2014-04-28T19:31:56.253 に答える