前置き
サーバーから受け取った画像のパスを、String型 → URL型に変換して
TableViewなりCollectionViewに表示するっていうことは結構多々あると思います。
今まで自作アプリを作る時になんとなく実装していた(AlamofireImageを使っていた)のが、いざ仕事で実装しようと思った時に、ライブラリがいろいろあるので、オーソドックスなやつを一通り触ってみました、というのが今回のきっかけ。
(追記 2017/8/1)
PINRemoteImageを比較対象に追加しました。
比較対象
以下の6つを用意した。
- URLSession(iosのデフォルト)
- AlamofireImage
- Nuke
- SDWebImage
- Kingfisher
- PINRemoteImage
選定基準は
- Pods/Carthageに対応していること
- 最終更新が3ヶ月以内であること(2017/7/26時点)
- 知名度(githubにそれなりのスター数があるとか)
前提条件
今回はすべての比較を画像の取得機能の一点において比較します。
アップロード、ダウンロード等ではまた変わってくるかと思うのでご了承ください。
比較1 〈機能〉
目的
- 用途に合わせた使い方でライブラリを選びたい
結果
以下にまとめてみた。(共通している機能は一部省いています。違ったらご指摘お願いします。)
| ①URLSession | ②AlamofireImage | ③Nuke | ④SDWebImage | ⑤Kingfisher | ⑥PINRemoteImage | |
|---|---|---|---|---|---|---|
| Placeholder | - | ○ | △ | ○ | ○ | ○ | 
| Image Transform | - | ○ | ○ | ○ | ○ | ○ | 
| Animation | - | ○ | ○ | △ | ○ | ○ | 
| Prefetch | - | - | △ | △ | ○ | ○ | 
Placeholder(プレースホルダー): 画像読み込み中に出す画像の機能(インジケーターとはまた違います)
ImageTransform(画像加工): 取得した画像を表示するまでに編集できる機能(例 サイズ、まる角など)
Animation(アニメーション): 画像出現の際にアニメーションをつける機能
Prefetch(プリフェッチ): 表示する画像をあらかじめ読み込んでおく機能
考察
- 
SDWebImageとKingfisherの機能はだいたい同じ
- 
NukeはPreheatで拡張すること、PINRemoteImageはデフォルトでプリフェッチ機能が使える。
- 
AlamofireImageは画像出現アニメーションが一番充実している。自分で作らなくてもデフォルトでいくつか用意されている。オプションで自作アニメーションも設定可能。(以下、用意されたアニメーション)
| crossDissolve | curlUp | curlDown | flipFromTop | flipFromBottom | flipFromLeft | flipFromRight | 
|---|---|---|---|---|---|---|
|   |   |   |   |   |   |   | 
比較2 〈書き方〉
目的
「書きやすさ」=「導入のしやすさ」=「可読性」に繋がると考え比較してみた。(個人的な主観です)
内容
比較しやすいように、できるだけ画像の取得方法を同じように記述しました。
(UIImageViewを拡張したExtensionで書いた。(指摘あればお願いします))
※今回は画像が取得できる前提のためエラーハンドリングは何も行っていません。
また書き方統一のためにエラーも1つに集約された形をとっています。(Requestを生成しないのもそのため)
それぞれレスポンスの型が違うため、結果の判定部分の書き方が同じにならない点にご了承ください。
①URLSession(iosのデフォルト)
extension UIImageView {
    func setImageByDefault(with url: URL) {
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            // Success
            if error == nil, case .some(let result) = data, let image = UIImage(data: result) {
                self?.image = image
            // Failure
            } else {
                // error handling
            }
        }.resume()
    }
}
ただ通信を行っているだけなので、レスポンスをUIImage(data:)でUIImageに変換しないといけない。
②AlamofireImage
import AlamofireImage
extension UIImageView {
    func setImageByAlamofire(with url: URL) {
        af_setImage(withURL: url) { [weak self] response in
            switch response.result {
            case .success(let image):
                self?.image = image
                
            case .failure(_):
                // error handling
                break
            }
        
        }
    }
}
他のライブラリと違い、レスポンスがタプルではなくSuccess/Failureで分岐できる。
③Nuke
import Nuke
extension UIImageView {
    func setImageByNuke(with url: URL) {
        
        Nuke.Manager.shared.loadImage(with: url, into: self) { [weak self] response, isResponse in
            // Success
            if isResponse, case .success(let image) = response {
                self?.image = image
            // Failure
            } else {
                // error handling
            }
            
        }
    }
}
引数がURL型とは別にinto: UIImageViewを指定する必要があるのが特徴。
(ただし、上記のようにExtensionなりでラップしてしまえば関係ないっちゃない)
④SDWebImage
import SDWebImage
extension UIImageView {
    func setImageBySDWebImage(with url: URL) {
        
        self.sd_setImage(with: url) { [weak self] image, error, _, _ in
            // Success
            if error == nil, let image = image {
                self?.image = image
                
            // Failure
            } else {
                // error handling
                
            }
        }
        
    }
}
ベースの書き方はURLSessionと同じ。UIImageに変換する手間がない。
⑤Kingfisher
import Kingfisher
extension UIImageView {
    func setImageByKingfisher(with url: URL) {
        self.kf.setImage(with: url) { [weak self] image, error, _, _ in
            // Success
            if error == nil, let image = image {
                self?.image = image
                
            // Failure
            } else {
                // error handling
            }
        }
        
    }
}
SDWebImageを元に書かれた言われてるだけあって、まんま同じに書けます笑
書き方の.kfっていうのがちょっとRxっぽい。
⑥PINRemoteImage
import PINRemoteImage
import PINCache
extension UIImageView {
    func setImageByPINRemoteImage(with url: URL) {
        
        self.pin_setImage(from: url) { [weak self] result in
            // Success
            if result.error == nil, let image = result.image {
                self?.image = image
            // Failure
            } else {
              // error handling
            }
            
        }
    }
}
戻り値はPINRemoteImageManagerResultの1つだが、結局中身を取り出して分岐する必要がある。
今回は、errorのnilチェックをしていますが、公式リファレンスを見ると let image = result.imageしかしてないのでなくても良いかと。
別途、PINCacheをimportしないとキャッシュが働きませんでした。
考察
- 
書き方を寄せてるとはいえ、基本的にどのライブラリも同じように書けるとわかった。 
- 
AlamofireImageがResult<T,Error>型のレスポンスのため、Success/Failureで簡潔に書ける印象。
 他の4つは、レスポンスがタプルで返され、エラーが存在するか(nilか)を先に判定する部分をワンクッション挟む必要があるため、若干冗長になってしまう印象。
(今回はプレースホルダーやリクエストなどのカスタマイズを一切行っていない書き方のため、偏っている可能性があるのはご了承ください。)
比較3 〈速度〉
目的
- ライブラリの処理速度を知りたい
- 特にキャッシュの性能で速度が変わるのかを知りたい
環境
- シュミレータ iOS10 iPhoneSE
検証内容
- 画像を表示する際、初期表示の画像取得速度
- 画像を表示する際、一度表示したものを再表示する時の画像取得速度
今回はCollectionViewに表示する想定で行っています。
補足
- 
2に関しては一度画面をスクロールしきってすべての画像が表示されてから、再度戻す場合に表示される画像のキャッシュ機能で、どの程度画像の取得速度が変わるのかを測定する。 
- 
計測する部分は「画像取得から、成功した時のコールバック時」までの時間を計測しているので、 CollectionViewの動作には左右されないようになっている。
- 
画像は事前に複数のURLセットした配列を用意している。 
結果
計150枚分の画像取得を計測した
→初回表示では15枚表示されるようにコレクションを作成したので、それを10回行って平均をとるため
画像1枚の取得平均
| 平均 | ①URLSession | ②AlamofireImage | ③Nuke | ④SDWebImage | ⑤Kingfisher | ⑥PINRemoteImage | 
|---|---|---|---|---|---|---|
| 初期表示 | 12.9 ms | 51.2 ms | 0.975 ms | 23.7 ms | 20.6 ms | 36.0 ms | 
| 再表示 | 1.2 ms(1 ms) | 0.273 ms | 0.130 ms | 0.146 ms | 0.180 ms | 0.134 ms | 
上記の結果から、初期表示に関しては、かなりばらつきが見られたが、再表示する際はキャッシュが働きかなり高速化されていることがわかる。
考察
- 結果として、Nukeが画像表示において一番早いことがわかった。
- 一見差があるように見えて、単位がms(1/1000秒)なので、ユーザーが不快に感じるような差は出ないように思える。ただ、画像取得と絡む形で、大量にデータがある場合や1画面で多くのAPIを叩いている場合は、その差が如実に出てくるかもしれないが、、。
後書き
Nukeが早い上にプリフェッチ機能もあるので、すばらしいなぁとは思いつつも、、、
現状、本家ライブラリにも記載はあるが、iOS10からUITableViewDataSourcePrefetching,UICollectionViewDataSourcePrefetching
が出たので、わざわざライブラリで実装する必要はないかもと思ったり。笑
導入のしやすさで行けば、AlamofireImageかと思います。
なんども記載していますが、今回は本当に一番簡単な方法で比較したので、素のライブラリを単純に比較しただけです。実際はリクエスト、キャッシュ部分のカスタマイズなど行ったりすることで、性能の差が出てくるんだろうなあ、と。
ご指摘あれば、ぜひ編集リクエストお願いいたします。m(_ _ )m
(追記 2017/8/1)
コメントで指摘されPINRemoteImageを調べてみましたが、機能も揃っており早い。
なによりPinterestより提供されているのでかなり信頼度も高いかと!
ライブラリのあれこれ
- AlamofireImage
iOSの画像をキャッシュする方法とAlamofireImageの解説
Swift製のURL画像取得ライブラリAlamofireImageを使って商品一覧画面を作成してみた
- Nuke
What is… Nuke? (コードは古い)
初めてOSSを読んでみた(Nuke)
- SDWebImage
SDWebImageでURLから画像を取得する
SwiftでSDWebImageを使ってダンロードした画像を加工してUIImageViewに表示する
- Kingfisher
SDWebImageライクなライブラリ、Kingfisherを使ってみる
[Swift]KingFisherで画像URLから画像データをダウンロード
- PINRemoteImage
SDWebImage と PINRemoteImage の速さを比較してみた
- その他
[iOS 10] UICollectionViewにおけるセルのライフサイクルの変更とPre-Fetching APIによるスムーズなスクロールについて