5

Grand Central Dispatchを使用してUITableViewCell非同期に画像をロードしています。これは、セルが再利用され、前のブロックが間違った画像をロードするいくつかの境界ケースを除いて、うまく機能します。

私の現在のコードは次のようになります。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (!cell) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    NSString *imagePath = [self imagePathForIndexPath:indexPath];         
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

    dispatch_async(queue, ^{
        UIImage *image = [UIImage imageWithContentsOfFile:imagePath];

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

    return cell;
}

私の知る限り、GCD キューは停止できません。では、どうすればこの国境事件を防ぐことができるでしょうか? または、この問題を解決するために GCD の代わりに何か他のものを使用する必要がありますか?

4

5 に答える 5

3

私が行ったことはNSOperation、セルにivarを追加することです。操作は、イメージの取得、ロード、および作成を担当します。完了すると、セルにpingを実行します。セルがデキューされた場合、操作はキャンセルされ、終了していない場合は破棄されます。でのキャンセルの動作テスト-main。画像がセルに渡された後、セルは操作をドロップして画像を使用します。

于 2012-08-13T09:28:52.027 に答える
1

非同期リクエストを開始する前に、イメージを強制的にリセットしてみてください。

ユーザーがテーブルをスクロールすると、非同期メソッドが正しい画像に変更する前に古い画像が表示される場合があります

この行を追加するだけです:

cell.imageView.image = nil; // or a placeHolder image
dispatch_async(queue, ^{
    UIImage *image = [UIImage imageWithContentsOfFile:imagePath];

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

編集:

@hgpc:

これで問題が解決するとは思いません。前のブロックは、新しいブロックの前に間違った画像を設定する可能性があります。– hgpc 9 分前

そうです、ユーザーのスクロールが速い場合、これは問題になる可能性があります...

私が見る唯一の方法は、各セルのキューに操作の数を(同期方法で)格納するカウンタープロパティを持つカスタムセルを作成し、非同期メソッドでカウンターが== 1であることを確認することです画像を変更します:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        static NSString *CellIdentifier = @"Cell";

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (!cell) {
    // MyCustomUITableViewCell has a property counter
            cell = [[[MyCustomUITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
            cell.counter = 0;
        }

        NSString *imagePath = [self imagePathForIndexPath:indexPath];         
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

        cell.imageView.image = nil; // or a placeHolder image
        cell.counter = cell.counter + 1;
        dispatch_async(queue, ^{
            UIImage *image = [UIImage imageWithContentsOfFile:imagePath];

            dispatch_sync(dispatch_get_main_queue(), ^{
                if (cell.counter == 1){
                    cell.imageView.image = image;
                   [cell setNeedsLayout];
                }
                cell.counter = cell.counter - 1;

            });
        });

    return cell;
}
于 2012-08-13T08:42:30.337 に答える
0

非同期タスクを起動する前にセル識別子を設定し、UI を更新する前に同じであることを確認できます。

于 2012-08-13T09:06:54.853 に答える
0

テーブルがスクロールしていないときにのみ画像をロードするのはどうですか? 問題cellForRowWithIndexPath:は、スクロールされているセルに対して多くの作業を行っていることです。

セルがキューから取り出されるたびに画像をロードする代わりに、 on viewDidLoad(またはデータ モデルが初期化されるたび) および onで実行できますscrollViewDidEndDecelerating:

-(void)viewDidLoad {
    [super viewDidLoad];
    [self initializeData];
    [self loadVisibleImages];
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if (scrollView == self.tableView) {
        [self loadVisibleImages];
    }
}

-(void)loadVisibleImages {
    for (NSIndexPath *indexPath in [self.tableview indexPathsForVisibleRows]) {
        dispatch_async(queue, ^{
            NSString *imagePath = [self imagePathForIndexPath:indexPath]; 
            UIImage *image = [UIImage imageWithContentsOfFile:imagePath];        
            dispatch_sync(dispatch_get_main_queue(), ^{
                UITableViewCell *cell = [self tableView:self.tableView cellForRowAtIndexPath:indexPath];
                cell.imageView.image = image;
                [cell setNeedsLayout];
            });
        });
    }
}
于 2012-08-14T06:22:17.843 に答える
0

ここにはいくつかの可能性があります。

a)NSOperationQueue直接 GDC の代わりに使用します。
NSOperation は GDC に基づいており、スレッドの停止などの多くの管理を可能にしました。
Apple の同時実行ガイドをご覧ください。

b) 準備ができている非同期イメージ ローダーを使用します。
Internetz で利用できる無料のオープン ソース プロジェクトは十分にあります。
私は個人的にAFNetworking(特にAFNetworking+UIImageViewカテゴリ)をお勧めします。

自分でやりたい場合(研究、知識など)は a )にとどまる必要がありますが、より便利な方法はb)です。

更新
ネットワークからではなく、ファイルからイメージをロードしていることに気付きました。
この場合、別の方法で処理し、次の点に注意する必要があります。

  • を避けimageWithContentsOfFile:て使用してくださいimageNamed:。これはimageNamed:、画像がロードされた後、キャッシュされimageWithContentsOfFile:ずにキャッシュされるためです。imageWithContentsOfFile:NSCache 内のすべての画像をプリロードし、このキャッシュからの画像をテーブルに入力する 必要がある場合。

  • TableView 画像のサイズ (寸法ファイルサイズ) は大きくしないでください。100x100 px の Retina 画像でさえ、非同期ロードが必要になるほど読み込みに時間がかからない数 kb を超えることはありません。

于 2012-08-13T09:37:18.420 に答える