[swift]挫折しながら覚えるiOS開発その5 TableViewのスクロールが遅い問題の対応(SDWebImage)


[まとめ] 現在開催中のKindleセール情報はこちら

前回はAPIから取得したデータをTableViewに表示できるところまで作成しました。

しかし、表示はできたもののTableViewをスクロールすると非常に動作が遅いという現象が発生しました。

今回はその原因の調査と対応を行います。

最初はViewController.swiftに書いたtableViewメソッドのdequeueReusableCellWithIdentifierメソッドが怪しいのではないかと思い、色々調べていたのですがよく分からなくなり挫折。。。

# ViewController.swift

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    # 再利用がうまくできていない? => そんなことなかったです
    var cell: LinkViewCell = tableView.dequeueReusableCellWithIdentifier("LinkViewCell") as! LinkViewCell  
    cell.setCell(links[indexPath.row])
    return cell
}

iosエンジニアの@ryusukefuda@seiya_vwに助けを求め、ちらっとコードを見てもらったところ、カスタムセルを作っているLinkViewCellのsetCellメソッド内で同期処理で画像を読み込もうとしているのが原因だと判明しました。

お二人ともアドバイスありがとうございます!

# LinkViewCell.swift

import UIKit

class LinkViewCell: UITableViewCell {

    @IBOutlet weak var title: UILabel!
    @IBOutlet weak var username: UILabel!
    @IBOutlet weak var userIcon: UIImageView!
    @IBOutlet weak var stockCount: UILabel!

    func setCell(link: Link) {
        self.title.text = link.title
        self.username.text = link.username
        self.stockCount.text = String(link.stockCount)

        if link.userIconUrl != nil {
            // ↓をコメントアウトして再ビルドすると快適に動作
            //let data = NSData(contentsOfURL: NSURL(string: link.userIconUrl!)!)
            //if data != nil {
            //    self.userIcon.image = UIImage(data: data!)
            //}
        }
    }
}

今回、200件分の画像取得をメインスレッド内ですべて行おうとしていたため、画像を読み込みつつスクロールをしようとしてiPhoneが固まったような現象が発生してしまったようです。

これはアプリ開発初心者がよくハマるポイントらしく、見事にひっかかってしまいました。

このような画像表示処理を行う場合は別スレッドにして非同期処理で行う必要がありました。

原因は分かったので、続いては画像表示処理を非同期で行うようコードを修正します。

画像表示を非同期で行う方法は色々あるらしいのですが、SDWebImageというライブラリを使うのがオススメと教えてもらったのでこちらを使うことにしました。

1. SDWebImageのインストール

PodfileにSDWebImageを追加します。

# Podfile

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Alamofire', '~> 1.2'
pod 'SDWebImage'  # <= 追加

pod installを実行

pod install

Xcodeに戻り、プロジェクト設定画面の一番下にある「Linked Frameworks and Libraries」の「+」を選択し、SDWebImageを追加します。

swift5-1

これでSDWebImageをXcode上に読み込むことができました。

2. swiftからSDWebImageが使えるように

ライブラリを読み込むところまではできたのですが、SDWebImageはObjective-Cで書かれたライブラリのため、swift経由で利用するにはBridging-Headerという仕組みを利用する必要があります。

Bridging-Headerに関しては下記のクックパッドさんのブログが参考になると教えてもらいました。

SwiftとObjective-Cのコードを1つのプロジェクトでつかう - クックパッド開発者ブログ

こちらの記事を参考にさせていただき設定していきます。

まずは「File」=>「New」=>「File」で新規ファイル追加を選択し、「Header File」を選択します。

続いてファイル名を「プロジェクト名-Bridging-Header.h」とします。

今回の場合だとプロジェクト名が「weeqiita」のため「weeqiita-Bridging-Header.h」となります。

swift5-2

Headerファイルを追加したら、SDWebImageのimport設定をHeaderファイルに記述します。

#import <SDWebImage/UIImageView+WebCache.h>

続いて、SDWebImageを全Swiftファイルから読み込めるように、以下のように「Build Settings」を変更します。

swift5-3

その後ビルドを行った際に

:0: error: bridging header '/Users/xxx/xxxxx/weeqiita-Bridging-Header.h' does not exist

というエラーが出た場合は、ファイルのパスが間違っているので正しいパスを確認します。

この時点でビルドが成功すればSDWebImageのインポートは正しく行えています。

3. SDWebImageを使って非同期に画像取得を行うように

いよいよ仕上げです。

LinkViewCellのsetCellメソッドで設定している、userIconの取得部分をsd_setImageWithURLメソッドに置き換えます。

import UIKit

class LinkViewCell: UITableViewCell {

    @IBOutlet weak var title: UILabel!
    @IBOutlet weak var username: UILabel!
    @IBOutlet weak var userIcon: UIImageView!
    @IBOutlet weak var stockCount: UILabel!

    func setCell(link: Link) {
        self.title.text = link.title
        self.username.text = link.username
        self.stockCount.text = String(link.stockCount)

        if link.userIconUrl != nil {
            self.userIcon.sd_setImageWithURL(NSURL(string: link.userIconUrl!))  # SDWebImageを使用して非同期処理に
        }
    }
}

SDWebImageを使うと非同期画像取得が1行で書けるので非常にお手軽ですね。

上記に変更した状態でビルドを行うと、スムーズなスクロールでTableViewを表示することができるようになりました。

ios4-3

「通信が必要な部分は非同期で」

次回からはこれを忘れないようにします。

次回はレイアウトの調整を行います。

参考

[まとめ] 現在開催中のKindleセール情報はこちら