SDWebImage 簡要介紹
SDWebImage 是一款性能卓越、流行度高的網絡圖片下載框架。對 SDWebImage 框架代碼解讀的文章數不勝數,這里只胡說八道框架的設計思想。
[注]:推薦南峰子的博客 《源碼解析:SDWebImage 實現分析》
《計算機組成與設計:硬件/軟件接口》 CPU 和 存儲管理講的很好。第三版、第四版均可。
框架設計思想剖析
SDWebImage 加載圖片的流程
流程參考這里 《那些著名或非著名的 iOS 面試題 - 前編》
1.入口 setImageWithURL:placeholderImage:options: 會先把 placeholderImage 顯示,然后 SDWebImageManager 根據 URL 開始處理圖片。
2.進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經下載 queryDiskCacheForKey:delegate:userInfo:.
3.先從內存圖片緩存查找是否有圖片,如果內存中已經有圖片緩存,SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4.SDWebImageManagerDelegate 回調 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。
5.如果內存緩存中沒有,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經緩存。
6.根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結果回調 notifyDelegate:。
7.如果上一操作從硬盤讀取到了圖片,將圖片添加到內存緩存中(如果空閑內存過小,會先清空內存緩存)。SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo:。進而回調展示圖片。
8.如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調 imageCache:didNotFindImageForKey:userInfo:。
9.共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
10.圖片下載由 NSURLConnection 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
11.connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
12.connectionDidFinishLoading: 數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
13.圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
14.在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調給 SDWebImageDownloader。
15.imageDownloader:didFinishWithImage: 回調給 SDWebImageManager 告知圖片下載完成。
16.通知所有的 downloadDelegates 下載完成,回調給需要的地方展示圖片。
17.將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。
18.SDImageCache 在初始化的時候會注冊一些消息通知,在內存警告或退到后臺的時候清理內存圖片緩存,應用結束的時候清理過期圖片。
總結
總體設計思路
- 內存中維護一個圖片緩存作為一級緩存,輔存中維護一個圖片緩存作為二級緩存。同時共享的下載器緩存下載任務,減少下載時的資源消耗。進行文件查找、文件下載、圖片解碼、文件讀寫操作等都使用后臺線程完成,減少對程序性能的影響。
設計經驗
- 設計模式
Observer、Delegate 、Singleton 的混合使用,使各功能組件的配合更加高效。這些簡單的設計模式也沒有那么不堪,關鍵是不亂套、不濫用。 - 性能優化
- 功能明確分開
文件查找、文件下載、文件讀寫、圖片解碼等操作都交由對應的功能組件完成,代碼清晰,結構明確。
- 功能明確分開
- 多線程的使用
處理必要的 UI 數據交換,其他的操作都盡可能的在后臺線程完成,減少對主線程的影響。 - 架構設計
- 多級緩存的靈活使用
內存中緩存圖片作為一級緩存,輔存中緩存圖片作為二級緩存;還對可能出現資源損耗的下載操作實現一次緩存。
[注] 緩存的使用不是越多多好,請慎重選擇使用。 - 功能組件的設置
將功能組件分開,各司其職。做到代碼結構清晰,功能有條不紊。(這里將‘文件下載’、‘文件IO’的操作稱為功能組件并不合適,只是為了便于理解思路。)
題外話
- 多線程
什么時候應該使用多線程? - 完成線程或者任務間的同步操作
- 減少多主線程的性能影響,提高反應速度。使用后臺線程。
多線程中應該考慮什么?
添加什么方式的任務到隊列中?同步方式還是異步方式?(同步任務和異步任務的區別是什么?為是否阻塞當前線程,以同步方式添加任務到隊列中會阻塞當前線程)
怎么去執行線程?串行還是并行?
3. 線程安全的?
典型的解決線程安全問題的方法:使用線程鎖實現。對應各有優缺點。
這里推薦解決線程安全問題幾篇文章:1.Objc 線程安全類的設計系列文章 2.多線程開發之線程安全 3.簡書上的 iOS 多線程開發-線程安全
[注]1. 一個典型的 GCD 死鎖問題
// 1 打印當前線程號
NSLog("之前 - %@", NSThread.currentThread())
// 2 在當前線程中,向主隊列中添加一個同步任務
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
NSLog("sync - %@", NSThread.currentThread())
})
// 3 沒有切換線程,在主線程中打印
NSLog("之后 - %@", NSThread.currentThread())
結果:只能輸出:之前 - XX
步驟分解:
1 在主線程中先獲取主線程隊列,以同步的方式添加一個任務到主線程隊列中。
2 主線程隊列中的任務會被取出串行執行,同時因為是同步方式,所以會阻塞當前主隊列線程
3 主線程被阻塞,不能被執行
[注] 2. 實現線程間數據同步或者通信
兩種實現方式:GCD 實現 和 NSOperation 實現。
- 緩存
強烈推薦閱讀《計算機組成與設計:硬件與軟件接口》。 - 架構
好的架構是一步一步進化出來的,而不是一次就完成所有架構的。但是不可否認,編寫代碼時,應該遵循良好的編碼規范和設計方法。
小技巧
- 不同網絡狀態下的圖片加載處理
場景:3/4G 和 WiFi 環境下的高清圖片和普通質量圖片的顯示問題
SDWebImage 會默認加載緩存中的高清圖片,為了節省用戶的流量,應該對網絡狀態進行判斷,控制加載不同質量的圖片。但是這還沒有完,有些用戶默認設置在 3/4 G 網絡環境下加載普通質量的圖片(設置在偏好設置中),該怎么進行省流處理?
例子:《iOS 開發-你真的會用 SDWebImage?》 - 下載進度展示
場景:正在下載的 GIF 圖片,被點擊瀏覽大圖,怎么保證小圖和大圖中顯示的下載進度是一致的? - SDWebImage 設置圖片圓角,避免離屏渲染
場景:設置圖片圓角,既要快,又不能有離屏渲染。
key: 在 ImageView 的分類中,開啟后臺線程使用 Q2D 進行圖片繪制。
任務
- 自己實現一個文件下載器
例子:《高效圖片輪播 - 兩個 UIImageView 實現》 - 斷點續傳
key:使用 HTTP 中的 HEAD 方法,設置每次下載的文件長度。
其他的方法請評論告知我,謝謝。