6

テーブル ビュー セルに画像を読み込んでいます。各セルには画像があります。以下のコードにいくつかのチュートリアルを適用しましたが、まだ速度が低下しています。

ドキュメント ディレクトリからこれらの画像を読み込んでいます。このプロセスをスピードアップするためのヒントやアイデアはありますか?

修正されたコードを編集:

Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.displayBeerName.text = beer.name;

// did we already cache a copy of the image?
if (beer.image != nil) {
    // good.  use it.  this will run quick and this will run most of the time
    cell.beerImage.image = beer.image;
} else {
    // it must be the first time we've scrolled by this beer.  do the expensive
    // image init off the main thread

    cell.beerImage.image  = nil;   // set a default value here.  nil is good enough for now

    [self loadImageForBeer:beer atIndexPath:indexPath];
}
- (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{

        UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath];
        beer.image = image;

        dispatch_sync(dispatch_get_main_queue(), ^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.beerImage.image = image;
        });
    });
}
4

4 に答える 4

12

あなたのアルゴリズムはかなり良さそうです。典型的な落とし穴の多くを回避しました。UI パフォーマンスの問題が解決しない場合は、次のことをお勧めします。

  1. 画像をメモリにキャッシュしてみてください。NSMutableArrayまたはを使用できますNSMutableDictionaryが、iOSアプリで画像をキャッシュする最良の方法は? Caleb はNSCache、プロセスを簡素化するクラスの利点について説明します。画像をキャッシュする場合は、メモリ不足に対応し、必要に応じてキャッシュを消去してください。didReceiveMemoryWarning通知センターの に返信したり、オブザーバーとして自分を追加したりできますUIApplicationDidReceiveMemoryWarningNotification

  2. キャッシュされた画像がサムネイル サイズであることを確認してください。そうしないと、UI が常に少しカクカクしてしまい (サイズ変更アルゴリズムが必要な場合はお知らせください)、不必要にメモリを使い果たしてしまいます。

  3. 画像の更新をメイン キューにディスパッチするときは、非同期で行う必要があります (ブロックがメイン キューに送り返されて終了するのを待つときに、バックグラウンド キューがぶらぶらしてリソースを拘束するのはなぜですか...高速スクロール中にバックアップされた画像がいくつかある場合は特に問題になります); と

  4. cellForRowAtIndexPathメイン キューにディスパッチするときは、取得元のセルがそうではないことを確認する必要がありますnil(セルのロード ロジックがバックアップされすぎた場合 (特に低速のデバイスで)、理論的には、問題のセルがスクロールされてオフになる可能性があるためです)。画面とアルゴリズムがクラッシュする可能性があります)。

私はあなたと非常によく似たアルゴリズムを使用しており、GCD 構造はほぼ同じで (上記の注意事項があります)、古いデバイスでも非常にスムーズにスクロールできます。コードを投稿してほしい場合は、喜んで投稿します。

それでも問題が解決しない場合は、CPU プロファイラーがボトルネックを特定し、どこに注意を向けるべきかを知らせるのに非常に役立ちます。Instruments を使用してパフォーマンスのボトルネックを特定する方法に焦点を当てた素晴らしい WWDC セッションがオンラインで利用できます。

これが私のコードです。でviewDidLoad、イメージ キャッシュを初期化します。

- (void)initializeCache
{
    self.imageCache = [[NSCache alloc] init];
    self.imageCache.name = @"Custom Image Cache";
    self.imageCache.countLimit = 50;
}

そして、私はこれを私の中で使用しますtableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ilvcCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // set the various cell properties

    // now update the cell image

    NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved

    UIImage *image = [self.imageCache objectForKey:imagename];

    if (image)
    {
        // if we have an cachedImage sitting in memory already, then use it

        cell.imageView.image = image;
    }
    else
    {
        cell.imageView.image = [UIView imageNamed:@"blank_image.png"];

        // the get the image in the background

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            // get the UIImage

            UIImage *image = [self getImage:imagename];

            // if we found it, then update UI

            if (image)
            {
                dispatch_async(dispatch_get_main_queue(), ^{

                    // if the cell is visible, then set the image

                    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                    if (cell)
                        cell.imageView.image = image;

                    [self.imageCache setObject:image forKey:imagename];
                });
            }
        });
    }

    return cell;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    [self.imageCache removeAllObjects];
}

余談ですが、別のスレッドにジャストインタイムで画像をロードするのではなく、キャッシュされた画像を別のキューにプリロードすることを検討する可能性のあるさらなる最適化の 1 つです。これは私にとって十分に高速であるように思われるため、必要ではないと思いますが、UI を高速化するためのもう 1 つのオプションです。

于 2012-07-16T06:14:02.203 に答える
1

欠落している手順は、フェッチされた画像でモデルを更新することです。現状では、毎回すべてのセルに対して新しいロードを実行しています。このモデルは、比較的高価な負荷の結果をキャッシュするのに適した場所です。Beer.imageプロパティを追加できますか?

次に、構成コードは次のようになります。

Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.displayBeerName.text = beer.name;

// did we already cache a copy of the image?
if (beer.image != nil) {
    // good.  use it.  this will run quick and this will run most of the time
    cell.beerImage.image = beer.image;
} else {
    // it must be the first time we've scrolled by this beer.  do the expensive
    // image init off the main thread

    cell.beerImage.image  = nil;   // set a default value here.  nil is good enough for now

    [self loadImageForBeer:beer atIndexPath:indexPath];
}

わかりやすくするために、ローダーロジックをここに移動しました...

- (void)loadImageForBeer:(Beer *)beer atIndexPath:(NSIndexPath *)indexPath {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{

        UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath];
        beer.image = image;

        dispatch_sync(dispatch_get_main_queue(), ^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.beerImage.image = image;
        });
    });
}
于 2012-07-14T20:57:08.223 に答える
1

ここで初期ロードのためにできることはあまりありませんが、ほぼ同じくらい高速です。それでも遅すぎる場合は、可能であれば小さい画像を読み込んでみてください。

いくつかのこと:

まず、-imageWithContentsOfFile には注意してください。何もキャッシュされません。-imageNamed とは対照的に、画像をロードするたびに完全なヒットを取得しています。これは、キャッシュ内で画像をウォームに保ちます。もちろん、それをドメイン オブジェクトにキャッシュすることもできますが、個人的にはそうしないよう強くお勧めします。Apple は -imageNamed を介して非常に優れたイメージ キャッシュを持っていますが、メモリ フットプリントは屋根を突き破り、独自のキャッシュ有効期限メカニズムを実装する必要があります。3つのデバイスファミリすべてでAppleよりも優れた仕事をすることができれば、私は驚くでしょう:)

次に、ここで UITableView の flyweight パターンを破っています。

dispatch_sync(dispatch_get_main_queue(), ^{
            cell.beerImage.image = image;
            beer.image = image;
            [cell setNeedsLayout];
        });

ブロック内のセルをキャプチャするのではなく、特定のインデックスでセルを提供するようにテーブル ビューに要求します。画像が読み込まれるまでに、そのセル インスタンスは実際には別のインデックス パスに再利用されている可能性があり、間違ったセルの画像。

ここでは -setNeedsLayout は必要ありません。画像を変更するだけで十分です。

編集:おっと!テーブルビューの画像で明らかなことを見逃しました。画像のサイズ、画像ビューのサイズ、画像のコンテンツ モードは? 画像のサイズが画像ビューとは大きく異なり、画像ビューのサイズを変更するように要求している場合、これはメイン スレッドで発生し、パフォーマンスが大幅に低下します。ロード後、画像をスレッド外の画像ビューにサイズ変更します (簡単な Google 検索で、それを行うためのコア グラフィック コードが得られます)。

于 2012-07-15T03:36:26.853 に答える
-1

以前にスタックオーバーフローで回答されたこの質問をご覧ください。

uitableViewcell の UIImage でテーブルのスクロールが遅くなる

または、このコードを試してください

- (void)configureCell:(BeerCell *)cell 
          atIndexPath:(NSIndexPath *)indexPath 
{
    Beer *beer = (Beer *) [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.displayBeerName.text = beer.name;

           UIImage *image = [UIImage imageWithContentsOfFile:beer.imagePath];

            cell.beerImage.image = image;
            [cell setNeedsLayout];
        }
于 2012-07-14T19:45:26.973 に答える