【iOS】tableViewのスクロールに合わせて画像を滑らかに表示したかった

おはこんばんちわ。
ドーモ。窓際エンジニア=デス

## 前振り
tableViewCellで画像を大量に使ったとき、困った件
何も考えずにSDWebImageで画像を非同期に読み込んでいたら、
スクロールした分のtableViewCell分だけ画像の読み込みが始まり、
見たい画像が表示されるまで時間がかかってしまった。

## 問題
1. スクロール中に表示されるtableViewCellの画像の読み込みむため、画像の読み込み処理が多く発生する
2. 画像の読み込み処理が積まれていくので、本当に見たい画像が表示されるまで時間がかかる

## 解決策
1. tableViewCellが見えなくなったら、tableViewCellの画像の読み込み処理を止める
2. スクロールしてないときに後続のtableViewCellの画像をprefetchしておく

## SDWebImageを普通に使う
SDWebImageが優秀なので、画像のdownloadの管理や画像のprefetchもできてしまう

### 1. tableViewCellの画像の読み込み処理を管理する
画像取得時にSDWebImageOperationオブジェクトを返している。このオブジェクトを管理している
例えばtableViewCellを再利用する際、それらのオブジェクトに画像読み込みキャンセルのメッセージを送ることで画像の読み込みを中断させる

```objc
@interface SampleTableViewCell ()
@property (strong, nonatomic) NSMutableArray *operations;

@end

@implementation SampleTableViewCell

-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self=[super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.operations = [NSMutableArray array];
    }
    return self;
}

// 例えば多くの画像を非同期で読み込む
-(void) loadImages:(NSArray *)imageURLs
{
    for ( NSURL *imageURL in imageURLs ) {
        id<SDWebImageOperation> operation = [[SDWebImageManager sharedManager] downloadImageWithURL:imageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL){
            // 画像の表示処理
        }];
        [self.operations addObject:operation];
    }
}

// cellが再利用させるときに呼ばれる
-(void)prepareForReuse
{
    [super prepareForReuse];

    // 画像の読み込みをキャンセル
    [self.operations enumerateObjectsUsingBlock:^(id<SDWebImageOperation> operation, NSUInteger idx, BOOL *stop){
        if (operation) {
            [operation cancel];
            operation = nil;
        }
    }];
    self.operations = [NSMutableArray array];
}

@end

```

これでtableViewをスクロールしまくっても大丈夫
スクロールして見えなくなったtableViewCellの分の画像読み込みはキャンセルされる

### 2. 画像をprefetchする
SDWebImageにはprefetcherも用意されている
`#import <SDWebImage/SDWebImagePrefetcher.h>` でインポートすれば使える
例えばtableViewのスクロールの開始/停止に合わせて画像を先読みする

```objc
#import <SDWebImage/SDWebImagePrefetcher.h>

@interface SampleTableView ()
@end

@implementation SampleTAbleView

/* 省略 */

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    // scroll中はprefetchしない、prefetchしてたら中止する
    [[SDWebImagePrefetcher sharedImagePrefetcher] cancelPrefetching];
}

-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
    // scroll中はprefetchしない、prefetchしてたら中止する
    [[SDWebImagePrefetcher sharedImagePrefetcher] cancelPrefetching];
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    // scrollが終わりそうになったら、prefetchし始める
    [self prefetchImages];
}

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate) {
        // scrollが終わりそうになったら、prefetchし始める
        [self prefetchImages];
    }
}

// 画像の先読みをする
-(void)prefetchImages
{
    // 先読みしたい画像のURLの配列
    NSArray *imageURLs = @[@"http://image1.jpg", @"http://image2.jpg"];

    [SDWebImagePrefetcher sharedImagePrefetcher] cancelPrefetching];
    [SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:imageURLs];
}

@end
```

これで、スクロールが止まっている間に画像の先読みをすることができます


## まとめ
tableViewで画像をたくさん使う際に知っておくと幸せになれそうなことをまとめました。
SDWebImageを利用して、画像の読み込みキャンセルと画像の先読みを実装する
画像の読み込みのキャンセルで、本当に見たい画像の読み込みが遅くならないように
画像の先読みで、あらかじめ画像を読み込んでおく
これでたくさんスクロールしても大丈夫そうです！