SDWebImage 底層實現原理以及面試題相關問題的學習

原文地址:http://blog.csdn.net/Maxdong24/article/details/53735205

IOS-SDWebImage 底層實現原理以及面試題相關問題的學習鏈接

SDWebImage/

uiimageview/

圖片

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,清除連接,并拋出下載停止的通知。如果下載成功,則會處理完整的圖片數據,對其進行適當的縮放與解壓縮操作,以提供給完成回調使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容