SDWebImage原理相關(guān)

一、SDWebImage的核心是:SDWebImageManger。SDWebImage的工作就是由它調(diào)度SDImageCache(一個處理緩存的類)和一個SDWebImageDownloader(負(fù)責(zé)下載網(wǎng)絡(luò)圖片)來完成的。
二、SDWebImage提供了如下三個category來進行緩存。
1.UIImageView + WebCache imageView的圖片
2.UIButton + WebCache 給按鈕設(shè)置圖片
3.MKAnnotationView + WebCache 地圖大頭針

\color{red}{三、工作流程:}

1.首先將placeholderImage進行展示,SDWebImageManager根據(jù)URL開始處理圖片

2.SDImageCache從緩存中查找圖片圖片,如果有SDImageCacheDelegate回調(diào)image:didFindImage:forkey:useInfo:給SDWebImageManager ,SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片

3.緩存中沒有,生成NSInvocationOperation添加到隊列中開始在硬盤中查找,
根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結(jié)果回調(diào) notifyDelegate:。
如果找到會將圖片添加到內(nèi)存緩存中(如果空閑緩存不夠,會先清理)然后SDImageCacheDelegate回調(diào)imageCache:didFindImage:forKey:userInfo:。進而回調(diào)展示圖片。

4.如果硬盤中沒有則共享或生成下載器SDWebImageDownLoader開始下載圖片,圖片下載由NSURLConnection來做,實現(xiàn)相關(guān) delegate 來判斷圖片下載中、下載完成和下載失敗。
connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理。

5.圖片解碼處理在一個NSOperationQueue完成,不會拖慢主線程UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。

6.在主線程notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給SDWebImageDownloader。
imageDownloader:didFinishWithImage: 回調(diào)給SDWebImageManager告知圖片下載完成

7.通知所有的downloadDelegates下載完成,回調(diào)給需要的地方展示圖片。將圖片保存到SDImageCache中,內(nèi)存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨NSInvocationOperation完成,避免拖慢主線程。

8.SDImageCache在初始化的時候會注冊一些消息通知,在內(nèi)存警告或退到后臺的時候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時候清理過期圖片,
當(dāng)應(yīng)用進入后臺時,會涉及到『Long-Running Task』正常程序在進入后臺后、雖然可以繼續(xù)執(zhí)行任務(wù)。但是在時間很短內(nèi)就會被掛起待機。
Long-Running可以讓系統(tǒng)為app再多分配一些時間來處理一些耗時任務(wù)。

流程圖如下


10405915-153e48f83e03898c.png

四、緩存要點
1.SDWebImage 實現(xiàn)了一個叫做 AutoPurgeCache 的類 繼承自 NSCache ,相比于普通的 NSCache, 它提供了一個在內(nèi)存緊張時候釋放緩存的能力。

自動刪除機制:當(dāng)系統(tǒng)內(nèi)存緊張時,NSCache 會自動刪除一些緩存對象
線程安全:從不同線程中對同一個 NSCache 對象進行增刪改查時,不需要加鎖
不同于 NSMutableDictionary、NSCache存儲對象時不會對 key 進行 copy 操作
@end

@implementation AutoPurgeCache

- (nonnull instancetype)init {
    self = [super init];
    if (self) {
#if SD_UIKIT
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end

2.通過內(nèi)部的一個枚舉可以看出磁盤不是強制寫入的


typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /**
     *  禁用磁盤緩存
     */ SDWebImageCacheMemoryOnly = 1 << 2,
}

3.緩存的時機有兩種情況:一個是在下載完成之后,自動保存,或者開發(fā)者通過代理處理完圖片并返回后緩存。二是當(dāng)緩存中沒有、但是從硬盤中查詢到了圖片。

4.磁盤的緩存時長默認(rèn)是一周,清除時間是當(dāng)程序退出到后臺、或者被殺死的時候,在后臺執(zhí)行耗時任務(wù)是要申請時間,不要一進入后臺短時間就被掛起

// 默認(rèn)緩存時長
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

// 清理時間
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(deleteOldFiles)
                                             name:UIApplicationWillTerminateNotification
                                           object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(backgroundDeleteOldFiles)
                                             name:UIApplicationDidEnterBackgroundNotification
                                           object:nil];
/*   磁盤清理的原則
首先、通過時間進行清理。(最后修改時間>一周)
然后、根據(jù)占據(jù)內(nèi)存大小進行清理。(如果占據(jù)內(nèi)存大于上限、則按時間排序、刪除到上限的1/2。)
由于在源碼中沒有找到給maxCacheSize設(shè)置最大顯示的代碼,所以猜測默認(rèn)沒有設(shè)置上限

*/
// 清理磁盤的方法
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

5.如何保證內(nèi)存和磁盤的讀寫安全?
a:NScache是線程安全的,在多線程操作中,不需要對Cache加鎖。
讀取緩存的時候是在主線程進行。所有不需要要擔(dān)心線程安全
b:磁盤的讀取雖然創(chuàng)建了一個NSOperation對象、但據(jù)我所見這個對象只是用來標(biāo)記該操作是否被取消、以及取消之后不再讀取磁盤文件的作用。
真正的磁盤緩存是在另一個IO專屬線程中的一個串行隊列下進行的。
如果你搜索self.ioQueue還能發(fā)現(xiàn)、不只是讀取磁盤內(nèi)容。
包括刪除、寫入等所有磁盤內(nèi)容都是在這個IO線程進行、以保證線程安全。
但計算大小、獲取文件總數(shù)等操作。則是在主線程進行。

- (void)checkIfQueueIsIOQueue {
    const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
    const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
    if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
        NSLog(@"This method should be called from the ioQueue");
    }
}

6.磁盤的路徑:默認(rèn)路徑
緩存在磁盤沙盒目錄下Library/Caches
二級目錄為~/Library/Caches/default/com.hackemist.SDWebImageCache.default
當(dāng)然也可自定義文件名

  1. 下載最大并發(fā)數(shù)、超時時長?
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadTimeout = 15.0;

8、緩存圖片命名

//1.寫入緩存時、直接用圖片url作為key
//寫入緩存 NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost]

// 2.寫入磁盤用url的MD5編碼作為key??梢苑乐刮募^長,以及避免重名

五、圖片格式和解碼
1.為什么下載和從磁盤中讀取的圖片要解碼?

一般下載或者從磁盤獲取的圖片是PNG或者JPG,這是經(jīng)過編碼壓縮后的圖片數(shù)據(jù),不是位圖,要把它們渲染到屏幕前就需要進行解碼轉(zhuǎn)成位圖數(shù)據(jù),而這個解碼操作比較耗時。
你也可以這么理解,圖片在遠(yuǎn)端存儲一定都是編碼后存儲的,這樣體積小,一個圖像可以看做是一個圖像文件,里面包含了文件頭,文件體和文件尾,圖像的數(shù)據(jù)就包含在文件體中,而我們的解碼就是運用算法將文件體中的圖像數(shù)據(jù)轉(zhuǎn)化為位圖數(shù)據(jù),方便渲染和展示。
iOS默認(rèn)是在主線程解碼,所以SDWebImage將這個過程放到子線程了。
同時因為位圖體積很大,所以磁盤緩存不會直接緩存位圖數(shù)據(jù),而是編碼壓縮后的PNG或JPG數(shù)據(jù)。

2.怎么判斷圖片格式?

將數(shù)據(jù)data轉(zhuǎn)為十六進制數(shù)據(jù),取第一個字節(jié)數(shù)據(jù)進行判斷。

3.如何播放gif圖片

3.1 把用戶傳入的gif圖片轉(zhuǎn)成NSData
3.2 根據(jù)該Data創(chuàng)建一個圖片數(shù)據(jù)源(NSData->CFImageSourceRef)
3.3 計算該數(shù)據(jù)源中一共有多少幀,把每一幀數(shù)據(jù)取出來放到圖片數(shù)組中
3.4 根據(jù)得到的數(shù)組+計算的動畫時間
3.5 [UIImage animatedImageWithImages:images duration:duration];

六、SDWebImage在多線程下載圖片時防止錯亂的策略
由于cell的重用機制,在我們加載出一個cell的時候imageView數(shù)據(jù)源開啟一個下載任務(wù)并返回一個image,當(dāng)cell重用時,其數(shù)據(jù)源又會開啟一個下載任務(wù)下載新的image,但關(guān)聯(lián)的對象是同一個imageView,這個時候直接setImage時會發(fā)生錯亂。

SDWebImage的處理是:
imageView對象會關(guān)聯(lián)一個下載列表(列表是給AnimationImages用的,這個時候會下載多張圖片),當(dāng)tableview滑動,imageView重設(shè)數(shù)據(jù)源(url)時,會cancel掉下載列表中所有的任務(wù),然后開啟一個新的下載任務(wù)。這樣子就保證了只有當(dāng)前可見的cell對象的imageView對象關(guān)聯(lián)的下載任務(wù)能夠回調(diào),不會發(fā)生image錯亂。
同時,SDWebImage管理了一個全局下載隊列(在DownloadManager中),并發(fā)量設(shè)置為6.也就是說如果可見cell的數(shù)目是大于6的,就會有部分下載隊列處于等待狀態(tài)。而且,在添加下載任務(wù)到全局的下載隊列中去的時候,SDWebImage默認(rèn)是采取LIFO(last in,first out)策略的,具體是在添加下載任務(wù)的時候,將上次添加的下載任務(wù)添加依賴為新添加的下載任務(wù)。

[wself.downloadQueue addOperation:operation];
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperationaddDependency:operation];
wself.lastAddedOperation = operation;
}

七、下載圖片失敗后的處理
使用

- (void)sd_setImageWithURL:(NSURL *)url
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder 

這兩個方法下載圖片,如果下載第一次 失敗了,再使用同樣的url 調(diào)用該方法,也不會進行第二次嘗試, 因為 SD會記錄 失敗的URL ,對它直接進行錯誤處理,

@synchronized (self.failedURLs) {
    isFailedUrl = [self.failedURLs containsObject:url];
}

好在 SD 有提供接口 靈活 處理這樣的情況,避免這樣的情況 是使用

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,                    // 值為2的0次方
    SDWebImageLowPriority = 1 << 1,                    // 值為2的1次方
    SDWebImageCacheMemoryOnly = 1 << 2,                // 值為2的2次方
    SDWebImageProgressiveDownload = 1 << 3,            // 值為2的3次方
    SDWebImageRefreshCached = 1 << 4,                  // 值為2的4次方
    SDWebImageContinueInBackground = 1 << 5,           // 值為2的5次方
    SDWebImageHandleCookies = 1 << 6,                  // 值為2的6次方
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,    // 值為2的7次方
    SDWebImageHighPriority = 1 << 8,
    SDWebImageDelayPlaceholder = 1 << 9,
    SDWebImageTransformAnimatedImage = 1 << 10,
    SDWebImageAvoidAutoSetImage = 1 << 11,
    SDWebImageScaleDownLargeImages = 1 << 12
};
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options 
// 由于SD提供的這個枚舉是位移枚舉,可以同時傳入多個,比如:
[icommageView sd_setImageWithURL:url placeholderImage:image options:SDWebImageRefreshCached | SDWebImageRetryFailed progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
     }];

注:本文是通過個人研讀SD源碼和參考其他博文總結(jié)的SD實現(xiàn)流程和使用過程中有可能遇到的問題,如有大佬發(fā)現(xiàn)有誤之處,歡迎指正??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容