原文:iOS image caching. Libraries benchmark (SDWebImage vs FastImageCache),譯者夜微眠(github地址),校對藍魂(博客)、Cocoa(博客)。
1.引言
過去的幾年裡,iOS應用在視覺方面越來越吸引人。圖像展示是其中很關鍵的部分,因為大部分圖像展示都需要下載並且渲染。大部分開發者都要使用圖像填充表格視圖(table views) 或者 集合視圖(collection views) 。下載圖片消耗一些資源(如蜂窩數據、電池以及CPU 等)。為了減少資源消耗,一些緩存模型也應運而生。
為了獲得良好的用戶體驗,當我們緩存和加載圖像時,了解iOS底層如何處理是很重要的。此外,大多數使用了圖片緩存的開源庫也是個不錯解決方案。
2.常用的解決途徑
異步下載圖像
處理圖像(拉伸,去紅眼,去邊框)以便展示
寫入磁盤
需要時從磁盤讀取並展示
// 假設我們有一個 NSURL *imageUrl and UIImageView *imageView, 我們需要通過NSURL下載圖片並在UIImageview上展示 if ([self hasImageDataForURL:imageUrl] { NSData *data = [self imageDataForUrl:imageUrl]; UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ imageView.image = image; }); } else { [self downloadImageFromURL:imageUrl withCompletion:^(NSData *imageData, …) { [self storeImageData:imageData …]; UIImage *image = [UIImage imageWithData:imageData]; dispatch_async(dispatch_get_main_queue(), ^{ imageView.image = image; }); }]; }
FPS 簡介
UI渲染理想情況FPS=60
60FPS => 16.7ms 每幀 這就意味著 如果任何主線程操作大於16.7ms,動態FPS將會下降,因為cpu忙於處理其他事情 而不是渲染UI。
3.常用解決途徑的缺點
從磁盤加載圖像或文件時間消耗昂貴(磁盤讀取比內存讀取慢大概10 - 1000倍,如果是SSD硬盤 可能與內存讀取速度更接近(大概慢10倍)。參考這裡的比較:Introduction to RAM Disks。如果使用SSD,將獲得接近內存的速度(大概比內存訪問速度慢十倍),但目前還沒有手機和平板集成SSD模塊。
創建UIImage實例將會在內存區生成一個圖片的壓縮版。但是壓縮後的圖像太小且無法渲染,如果我們從磁盤加載圖像,圖像甚至都沒有加載到內存。解壓圖片同樣也很消耗資源。
設置imageView的image屬性,這種情況下將會創建一個CATransaction並加入主循環中。在下一次循環迭代中,CATransaction會對任何設置為layer contents的圖像進行拷貝。
拷貝圖像包含以下過程:
給文件io 和 解壓縮 分配緩沖區
讀取磁盤數據到內存
解壓圖像數據(生成原位圖) - 高cpu消耗
CoreAnimation 使用解壓數據並渲染
字節位沒有正確對齊的圖像將被CoreAnimation拷貝,以修復字節位對齊並使之能被渲染。這一點在Apple 文檔裡沒有說明,但是使用Instruments表明 CA::Render::copy_image會執行此操作,即使Core Aniation 即使沒有拷貝圖像。
從iOS7 開始,第三方應用不能使用JPEG硬件解碼器。這意味著我們只能使用慢很多的軟解碼器。這一點在FastImageCache團隊的 GitHub主頁以及 Nick Lockwood的推文上都有指出。
4.一個強大的iOS圖像緩存需包含以下部分:
異步下載圖像,盡可能減少使用主線程隊列。
使用後台隊列解壓圖像。這是個復雜的過程,請閱讀Avoiding Image Decompression Sickness(http://www.cocoanetics.com/2011/10/avoiding-image-decompression-sickness/)。
在內存和磁盤上緩存圖像。在磁盤上緩存圖像很重要,因為app可能因為內存不足而被強行關閉或者需要清理內存。這種情況下,重新從磁盤加載圖像比下載會快很多。備注:如果使用NSCache作為內存緩存,當有內存警告時,NSCache會清空緩存內容。NSCache相關細節請查看nshipster 上這篇文章:NSCache
保存解壓過的圖片到硬盤以及內存中,以避免再次解壓。
使用GCD 和 blocks,這將使得代碼更加高效和簡單。如今GCD 和 blocks是異步操作時必需的。
最好使用UIImageView的分類以便集成
最好在下載後以及存入到緩存前能夠處理圖像
iOS圖像優化
更多的成像相關以及SDK框架(CoreGraphics,ImageIO,CoreAnimation,CoreImage)工作原理,CPU vs GPU 等,請閱讀@rsebbe的文章:Advanced Imaging on iOS
Core Data 是一個好的選擇嗎?
這有一篇文章--CoreData 對比File System,實現圖像緩存的基准測試。結果File System的表現更好(正如我們所預期的)
看一看上面羅列的觀點,自己實現圖像緩存不僅復雜,耗時而且痛苦。這也是為什麼我傾向於使用開源的圖像緩存解決方案。你們大部分已經聽說過SDWebImage或new FastImageCache。
為了讓你知道哪個開源庫最適合你,我做了測試並且分析它們如何滿足上述要求。
5.基准測試
測試庫:
SDWebImage - version 3.5.4
FastImageCache - version 1.2
AFNetworking - version 2.2.1
TMCache - version 1.2.0
Haneke - version 0.0.5
注:AFNetworking 加入對比是由於其自iOS7後在磁盤緩存方面出色的表現(基於NSURLCache實現)
測試場景
對於每個庫,我都會使用全新的測試app,然後啟動app,等所有圖像加載完後,慢慢滑動。然後以不同力度來回滑動(從慢到快)。接著關掉app強制應用從磁盤緩存中加載圖像,最後重復以上測試場景。
關於測試app工程
-相關demo可以在Github找到並獲取,名字是ImageCachingBenchmark。同時還有本次實驗的圖表、結果數據表以及更多。
-請注意,請注意Github上的工程和圖像緩存庫都需要做一些調整,以便能讓我們看到每一張緩存的圖片都能夠被加載出來。由於我不想檢查Cocoapods源碼文件(不是個好習慣),所以需要對Cocoapods clean後重新編譯工程代碼 。目前Github上的版本與我做測試的版本有些差別。
-如果你們想重新跑一下測試,你需要編寫相同completionBlock用於圖像加載,所有庫得要跟默認的SDWebImage一樣返回SDImageCacheType。
最快與最慢的設備對比結果
在Github工程上能看到完整的基准測試結果,由於這些表格很大,我只使用運行最快的設備iPhone 5s和運行最慢的iPhone 4來測試。
匯總:
表格名詞解釋
- 異步下載 = 庫支持異步下載
-後台解壓 =通過後台隊列或線程執行圖像解壓
-存儲解壓 = 存儲解壓後的圖像版本
-內存/磁盤緩存 = 支持內存/磁盤緩存
-UIImageView分類 = 庫中含UIImageView 類別
-從內存/磁盤 = 從緩存(內存/磁盤)中讀取的平均時間
6.結論
-從頭開始編寫iOS圖像緩存組件很困難
-SDWebImage 和 AFNetworking 是穩定的工程。由於有很多貢獻者,這樣保證代碼能夠及時得到維護。FastImageCache在維護方面更新很快。
-基於以上所有數據,我認為SDWebImage 在目前是一個很好的解決方案。即使有些工程使用AFNetworking 或 FastImageCache更好。但是這些都依賴於項目需求。