tableView:cellForRowAtIndexPath:でGCDのBlocksにキャプチャするのはcellではなくindexPathが正解だった

長いタイトルですが。。
UITableViewCellに画像を表示する時に、画像の読み込みに時間がかかるので、GCDで非同期処理をする時の話です。

画像を表示するのがcellなので、次のようなコードを書いていたのですが、これは間違いでした。
このコードでは、UITableViewが高速にスクロールされた時などに、画像の読み込みが終わる前に画面上からいなくなり、Re-Useされることがあります。そうなってしまうと、Re-Useされる前に表示するはずだった画像がRe-Use後に表示する画像の前に一瞬だけ表示されてしまい、見た目がちょっとかっこ悪くなります。

```objc:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    NSString *path = self.pathArray[indexPath.row];
    dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(q_global, ^{
        // 重い処理
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
        dispatch_queue_t q_main = dispatch_get_main_queue();
        dispatch_async(q_main, ^{
            // cellをキャプチャ
            // -> 重い処理の後には、別のindexPathのcellかもしれない
            cell.imageView.image = image;
        });
    });
}
```

何か良い方法はないものかと調べていたら、このブログ記事を見つけました。→[UITableViewCellで画像を非同期でロードしようとしたときに困った事②](http://nyan-poppo.hatenablog.com/entry/2012/04/24/013301)
この記事を読んでいて、cellではなくindexPathをキャプチャすれば解決することに気づきました。
ソースコードを書き直すと、次のようになります。このコードであれば、目的のindexPathのcellに画像を表示することができます。
なお、cellForRowAtIndexPath:メソッドは、indexPathが示すcellが画面上に表示されていなければnilを返します。

**[2013/7/21 0:35 追記]
UITableView#cellForRowAtIndexPath:だと、画面上に表示されていてもnilが返される事があるとのコメントを頂きました。
画面上のcellにもれなく画像を表示できるよう、indexPathだけでなくcellもキャプチャするようソースを修正しました。
また、cellに現在のindexPathを持たせるために、カテゴリを使用してUITableViewCellクラスを拡張しています。**

```objc:UITableViewCell+CurrentIndexPath.h
#import <UIKit/UIKit.h>

// UITableViewCellクラスを拡張し、currentIndexPathプロパティを追加
@interface UITableViewCell (CurrentIndexPath)

@property (nonatomic, strong) NSIndexPath *currentIndexPath;

@end
```

```objc:UITableViewCell+CurrentIndexPath.m
#import "UITableViewCell+CurrentIndexPath.h"
#import <objc/runtime.h>

static char kCurrentIndexPathKey;

// currentIndexPathプロパティの実装
@implementation UITableViewCell (CurrentIndexPath)

// getter
- (NSIndexPath *)currentIndexPath
{
    return objc_getAssociatedObject(self, &kCurrentIndexPathKey);
}

// setter
- (void)setCurrentIndexPath:(NSIndexPath *)currentIndexPath
{
    objc_setAssociatedObject(self, &kCurrentIndexPathKey, currentIndexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
```

```objc:
// #import "UITableViewCell+CurrentIndexPath.h"

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    // cellをデキューしたら、現在のindexPathを持たせる
    cell.currentIndexPath = indexPath
    NSString *path = self.pathArray[indexPath.row];
    dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(q_global, ^{
        // 重い処理
        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
        dispatch_queue_t q_main = dispatch_get_main_queue();
        dispatch_async(q_main, ^{
            // cellとindexPathをキャプチャ
            //  -> cellが持っているindexPathとキャプチャしたindexPathが等しい場合のみ画像をセット
            if ([cell.currentIndexPath isEqual:indexPath]) {
                cell.imageView.image = image;
            }
        });
    });
}
```