原文地址:http://blog.csdn.net/Maxdong24/article/details/53735205
IOS-SDWebImage 底層實現原理以及面試題相關問題的學習鏈接
781
<推薦幾個我學習SDWebImage的鏈接>
SDWebImage實現詳解:
http://www.cnblogs.com/YYSheng/articles/4735609.html
有關請求相同地址圖片的下載問題(SDWebIMage底層原理的實現):
http://www.lxweimin.com/p/e00854ab5567
SDWebImage的底層介紹以及——需要了解和掌握的要點(包括面試題,圖片格式,以及GIF的播放):
http://blog.csdn.net/u013087513/article/details/49402427
SDWebImage內部實現過程:
http://blog.csdn.net/ab20514/article/details/49479505
SDWebImageDownloader中線程安全:
http://liuyuxiao.cc/index.php/2016/05/30/sdwebimagedownloader-thread-safe/
SDWebImage 加載圖片的流程
入口 setImageWithURL:placeholderImage:options: 會先把 placeholderImage 顯示,然后 SDWebImageManager 根據 URL 開始處理圖片。
進入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經下載 queryDiskCacheForKey:delegate:userInfo:.
先從內存圖片緩存查找是否有圖片,如果內存中已經有圖片緩存,SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
SDWebImageManagerDelegate 回調 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。
如果內存緩存中沒有,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經緩存。
根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結果回調 notifyDelegate:。
如果上一操作從硬盤讀取到了圖片,將圖片添加到內存緩存中(如果空閑內存過小,會先清空內存緩存)。SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo:。進而回調展示圖片。
如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調 imageCache:didNotFindImageForKey:userInfo:。
共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
圖片下載由 NSURLConnection 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
connectionDidFinishLoading: 數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調給 SDWebImageDownloader。
imageDownloader:didFinishWithImage: 回調給 SDWebImageManager 告知圖片下載完成。
通知所有的 downloadDelegates 下載完成,回調給需要的地方展示圖片。
將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。
SDImageCache 在初始化的時候會注冊一些消息通知,在內存警告或退到后臺的時候清理內存圖片緩存,應用結束的時候清理過期圖片。
SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
SDWebImagePrefetcher 可以預先下載圖片,方便后續使用
SDWebImage庫的作用
通過對UIImageView的類別擴展來實現異步加載替換圖片的工作。
主要用到的對象:
1、UIImageView (WebCache)類別,入口封裝,實現讀取圖片完成后的回調
2、SDWebImageManager,對圖片進行管理的中轉站,記錄那些圖片正在讀取。
向下層讀取Cache(調用SDImageCache),或者向網絡讀取對象(調用SDWebImageDownloader) 。
實現SDImageCache和SDWebImageDownloader的回調。
3、SDImageCache,根據URL的MD5摘要對圖片進行存儲和讀取(實現存在內存中或者存在硬盤上兩種實現)
實現圖片和內存清理工作。
4、SDWebImageDownloader,根據URL向網絡讀取數據(實現部分讀取和全部讀取后再通知回調兩種方式)
其他類:
SDWebImageDecoder,異步對圖像進行了一次解壓??
解惑
1、SDImageCache是怎么做數據管理的?
SDImageCache分兩個部分,一個是內存層面的,一個是硬盤層面的。
內存層面的相當是個緩存器,以Key-Value的形式存儲圖片。當內存不夠的時候會清除所有緩存圖片。
用搜索文件系統的方式做管理,文件替換方式是以時間為單位,剔除時間大于一周的圖片文件。
當SDWebImageManager向SDImageCache要資源時,先搜索內存層面的數據,如果有直接返回,沒有的話去訪問磁盤,將圖片從磁盤讀取出來,然后做Decoder,將圖片對象放到內存層面做備份,再返回調用層。
2、為啥必須做Decoder?
通過這個博客:http://www.cocoanetics.com/2011/10/avoiding-image-decompression-sickness/
現在明白了,由于UIImage的imageWithData函數是每次畫圖的時候才將Data解壓成ARGB的圖像,
所以在每次畫圖的時候,會有一個解壓操作,這樣效率很低,但是只有瞬時的內存需求。
為了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓,然后畫在另外一張圖片上,這樣這張新圖片就不再需要重復解壓了。
這種做法是典型的空間換時間的做法。
SDWebImage是一個圖片緩存的框架。相較于AFNetworking集成的UIImageView+AFNetworking.h,對于圖片的緩存實際應用的是NSURLCache自帶的cache機制。
NSURLCache每次都要把緩存的raw data 再轉化為UIImage,就帶來了數據處理和內存方面的更多操作。
SDWebImage的緩存由SDImageCache類來實現,這是一個單例類,該類負責處理內存緩存及一個可選的磁盤緩存,其中磁盤緩存的寫操作是異步的,這樣就不會對UI操作造成影響。此外還提供了若干屬性和接口來配置和操作緩存對象。包含以下功能:
1.提供UIImageView的一個分類,以支持網絡圖片的加載與緩存管理
2.一個異步的圖片加載器
3.一個異步的內存+磁盤圖片緩存
4.支持GIF圖片
5.支持WebP圖片
6.后臺圖片解壓縮處理
7.確保同一個URL的圖片不被下載多次
8.確保虛假的URL不會被反復加載
9.確保下載及緩存時,主線程不被阻塞
SDWebImage底層實現原理:
SDWebImage有沙盒緩存機制,主要由三塊組成
1.內存圖片緩存
2.內存操作緩存
3.磁盤沙盒緩存
SDWebImage的大部分工作是由緩存對象SDImageCache和異步下載器管理對象SDWebImageManager來完成的。SDWebImage的圖片下載是由SDWebImageDownloader這個類來實現的,它是一個異步下載管理器,下載過程中增加了對圖片加載做了優化的處理。而真正實現圖片下載的是自定義的一個Operation操作,將該操作加入到下載管理器的操作隊列downloadQueue中,Operation操作依賴系統提供的NSURLConnection類實現圖片的下載。
SDWebImage提供了對圖片緩存的支持,而該功能是由SDImageCache類來完成的。該類負責處理內存緩存及一個可選的磁盤緩存。內存緩存的處理是使用NSCache對象來實現的。NSCache是一個類似于集合的容器。它存儲key-value對,這一點類似于NSDictionary類,用搜索文件系統的方式做管理,文件替換方式是以時間為單位。我們通常用使用緩存來臨時存儲短時間使用但創建昂貴的對象。重用這些對象可以優化性能,因為它們的值不需要重新計算。另外一方面,這些對象對于程序來說不是緊要的,在內存緊張時會被丟棄。
磁盤緩存的處理則是使用NSFileManager對象來實現的。圖片存儲的位置是位于Cache文件夾。另外,SDImageCache還定義了一個串行隊列,來異步存儲圖片。
當SDWebImageManager向SDImageCache要資源時,先搜索內存層面的數據,如果有直接返回,沒有的話去訪問磁盤,將圖片從磁盤讀取出來,然后做Decoder,將圖片對象放到內存層面做備份,再返回調用層。使用Decoder 是因為UIImage的imageWithData函數是每次畫圖的時候才將Data解壓成ARGB的圖像,
所以在每次畫圖的時候,會有一個解壓操作,這樣效率很低,但是只有瞬時的內存需求。
為了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓,然后畫在另外一張圖片上,這樣這張新圖片就不再需要重復解壓了。是典型的空間換時間的做法。
SDWebImage的原理
1.使用
-(void)sd_setImageWithURL:(NSURL)url placeholderImage:(UIImage)placeholder options:(SDWebImageOptions)options;
會先把 placeholderImage 顯示,然后 SDWebImageManager 根據 URL 開始處理圖片。
2.進入 SDWebImageManager
-downloadWithURL:options:progress:completed:
交給 SDImageCache 從緩存查找圖片是否已經下載。
3.先從內存圖片緩存查找是否有圖片,如果內存中已經有圖片緩存,取緩存,沒有從- (UIImage )diskImageForKey:(NSString )key去磁盤緩存中去查找,根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。在磁盤緩存中找到后,同時更新置內存緩存中(如果空閑內存過小,會先清空內存緩存),有回調則調用doneBlock回調。
4.找到了就從SDWebImageQueryCompletedBlock到 UIImageView+WebCache 等前端展示圖片。
5.如果從硬盤緩存目錄讀取不到圖片,說明不存在該圖片,需要下載圖片,共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。 圖片下載由 NSURLSession 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
6.URLSession:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
7.圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
8.在主線程 SDWebImageDownloaderCompletedBlock里處理解碼完成后的操作。回調給需要的地方展示圖片。
9.從SDWebImageDownloaderProgressBlock 回調給 SDWebImageManager 告知圖片下載信息。
10.將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。
11.SDImageCache 在初始化的時候會注冊一些消息通知,在內存警告或退到后臺的時候清理內存圖片緩存,應用結束的時候清理過期圖片。
12.SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache方便使用。 SDWebImagePrefetcher 可以預先下載圖片,方便后續使用。
SDWebImage 使用
常用到的對象:
1、UIImageView (WebCache)類別,入口封裝,實現讀取圖片完成后的回調。
2、SDWebImageManager,對圖片進行管理的中轉站,記錄那些圖片正在讀取。
向下層讀取Cache(調用SDImageCache),或者向網絡讀取對象(調用SDWebImageDownloader) 。
實現SDImageCache和SDWebImageDownloader的回調。
3、SDImageCache,根據URL的MD5摘要對圖片進行存儲和讀取(實現存在內存中或者存在硬盤上兩種實現)
實現圖片和內存清理工作。
4、SDWebImageDownloader,根據URL向網絡讀取數據(實現部分讀取和全部讀取后再通知回調兩種方式)
5、SDWebImageDecoder,異步對圖像進行了一次解壓
使用:
[self.imageView sd_setImageWithURL:self.imageURL? ? ? ? ? ? ? ? ? ? ? ? placeholderImage:nil? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? options:SDWebImageProgressiveDownload? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? progress:^(NSInteger receivedSize, NSInteger expectedSize) {...}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {...}];
1
2
3
4
5
6
7
8
9
調用setImageWithURL:方法的時候,SDWebImage自動做很多事,當你需要在某一具體時刻做事情的時候,你可以覆蓋這些方法。比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:
//這個方法是下載imagePath2的時候響應SDWebImageManager *manager = [SDWebImageManager sharedManager];? [manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSIntegerreceivedSize,NSIntegerexpectedSize) {NSLog(@"顯示當前進度");? } completed:^(UIImage*image,NSError*error, SDImageCacheType cacheType,BOOLfinished,NSURL*imageURL) {NSLog(@"下載完成");? }];
1
2
3
4
5
6
7
8
9
10
11
基本代碼:
使用SDWebImageManager類:可以進行一些異步加載的工作。
SDWebImageManager *manager = [SDWebImageManager sharedManager];UIImage*cachedImage = [manager imageWithURL:url];// 將需要緩存的圖片加載進來if(cachedImage) {// 如果Cache命中,則直接利用緩存的圖片進行有關操作// Use the cached image immediatly}else{// 如果Cache沒有命中,則去下載指定網絡位置的圖片,并且給出一個委托方法// Start an async download[manager downloadWithURL:url delegate:self];? }
1
2
3
4
5
6
7
8
9
10
當然你的類要實現SDWebImageManagerDelegate協議,并且要實現協議的webImageManager:didFinishWithImage:方法。
// 當下載完成后,調用回調方法,使下載的圖片顯示- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage*)image {// Do something with the downloaded image}
1
2
3
4
獨立的異步圖像下載
可能會單獨用到異步圖片下載,則一定要用downloaderWithURL:delegate:來建立一個SDWebImageDownloader實例。
downloader = [SDWebImageDownloader downloaderWithURL:urldelegate:self];
1
這樣SDWebImageDownloaderDelegate協議的方法imageDownloader:didFinishWithImage:被調用時下載會立即開始并完成。
獨立的異步圖像緩存
SDImageCache類提供一個創建空緩存的實例,并用方法imageForKey:來尋找當前緩存。
UIImage *myCachedImage=[[SDImageCache sharedImageCache] imageFromKey:myCacheKey];
1
存儲一個圖像到緩存是使用方法storeImage: forKey:
[[SDImageCache sharedImageCache]storeImage:myImageforKey:myCacheKey];
1
默認情況下,圖像將被存儲在內存緩存和磁盤緩存中。如果僅僅是想內存緩存中,要使用storeImage:forKey:toDisk:方法的第三個參數帶一負值
來替代。
SDWebImage 源碼分析示例
SDWebImageDownloader類
SDWebImageDownloaderOptions定義:
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {SDWebImageDownloaderLowPriority= 1 << 0,SDWebImageDownloaderProgressiveDownload= 1 << 1,? // 默認情況下請求不使用NSURLCache,如果設置該選項,則以默認的緩存策略來使用NSURLCacheSDWebImageDownloaderUseNSURLCache= 1 << 2,? // 如果從NSURLCache緩存中讀取圖片,則使用nil作為參數來調用完成blockSDWebImageDownloaderIgnoreCachedResponse= 1 << 3,? // 在iOS 4+系統上,允許程序進入后臺后繼續下載圖片。該操作通過向系統申請額外的時間來完成后臺下載。如果后臺任務終止,則操作會被取消SDWebImageDownloaderContinueInBackground= 1 << 4,? // 通過設置NSMutableURLRequest.HTTPShouldHandleCookies = YES來處理存儲在NSHTTPCookieStore中的cookieSDWebImageDownloaderHandleCookies= 1 << 5,? // 允許不受信任的SSL證書。主要用于測試目的。SDWebImageDownloaderAllowInvalidSSLCertificates= 1 << 6,? // 將圖片下載放到高優先級隊列中SDWebImageDownloaderHighPriority= 1 << 7,? };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
下載順序:
typedefNS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {// 以隊列的方式,按照先進先出的順序下載。這是默認的下載順序SDWebImageDownloaderFIFOExecutionOrder,// 以棧的方式,按照后進先出的順序下載。SDWebImageDownloaderLIFOExecutionOrder? };
1
2
3
4
5
6
每個下載操作都定義了回調操作,如下載進度回調,下載完成回調,頭部過濾等,這些回調操作是以block形式來呈現;每個下載操作的下載進度回調和下載完成回調,這兩個回調稍后將保存在下載管理器的URLCallbacks字典中,key為URL,value為一個數組,數組里面又存放一個保存了下載進度回調和完成回調代碼塊的字典。這個字典數組同時也保證了同一張圖片只會被下載一次。
// 下載進度typedefvoid(^SDWebImageDownloaderProgressBlock)(NSIntegerreceivedSize,NSIntegerexpectedSize);// 下載完成typedefvoid(^SDWebImageDownloaderCompletedBlock)(UIImage*image, NSData *data,NSError*error,BOOLfinished);// Header過濾typedefNSDictionary*(^SDWebImageDownloaderHeadersFilterBlock)(NSURL*url,NSDictionary*headers);
1
2
3
4
5
6
為了保證URLCallbacks操作(添加、刪除)的線程安全性,SDWebImageDownloader將這些操作作為一個個任務放到barrierQueue隊列中,并設置屏障來確保同一時間只有一個線程操作URLCallbacks屬性。
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {...//1.以dispatch_barrier_sync操作來保證同一時間只有一個線程能對URLCallbacks進行操作? dispatch_barrier_sync(self.barrierQueue, ^{...//2.處理同一URL的同步下載請求的單個下載? });? }
1
2
3
4
5
6
7
8
下載請求的管理都是放在downloadImageWithURL:options:progress:completed:方法里面來處理的,該方法調用了上面所提到的addProgressCallback:andCompletedBlock:forURL:createCallback:方法來將請求的信息存入管理器中,同時在創建回調的block中創建新的操作,配置之后將其放入downloadQueue操作隊列中,最后方法返回新創建的操作。
- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {...[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{...}
1
2
3
4
5
下載操作的超時時間可以通過downloadTimeout屬性來設置,默認值為15秒。
SDWebImage定義了一個協議,即 SDWebImageOperation 作為圖片下載操作的基礎協議。它只聲明了一個cancel方法,用于取消操作。每個圖片的下載都是一個Operation操作。SDWebImage自定義了一個Operation類,即 SDWebImageDownloaderOperation ,它繼承自NSOperation,并采用了SDWebImageOperation協議。除了繼承而來的方法,該類只向外暴露了一個方法,initWithRequest:options:progress:completed:cancelled:。對于圖片的下載,SDWebImageDownloaderOperation完全依賴于URL加載系統中的NSURLSession。具體看代碼源碼分析.
-(void)URLSession:(NSURLSession*)session dataTask:(NSURLSessionDataTask*)dataTask didReceiveData:(NSData*)data
1
方法的主要任務是接收數據。每次接收到數據時,都會用現有的數據創建一個CGImageSourceRef對象以做處理。在首次獲取到數據時(width+height==0)會從這些包含圖像信息的數據中取出圖像的長、寬、方向等信息以備使用。而后在圖片下載完成之前,會使用CGImageSourceRef對象創建一個圖片對象,經過縮放、解壓縮操作后生成一個UIImage對象供完成回調使用。當然,在這個方法中還需要處理的就是進度信息。如果我們有設置進度回調的話,就調用這個進度回調以處理當前圖片的下載進度。
縮放操作可以查看SDWebImageCompat文件中的SDScaledImageForKey函數;解壓縮操作可以查看SDWebImageDecoder文件+decodedImageWithImage方法。在下載完成或下載失敗后,需要停止當前線程的run loop,清除連接,并拋出下載停止的通知。如果下載成功,則會處理完整的圖片數據,對其進行適當的縮放與解壓縮操作,以提供給完成回調使用。