SDWebImage原理相關(guān)

一、SDWebImage的核心是:SDWebImageManger。SDWebImage的工作就是由它調(diào)度SDImageCache(一個處理緩存的類)和一個SDWebImageDownloader(負責下載網(wǎng)絡圖片)來完成的馍悟。
二、SDWebImage提供了如下三個category來進行緩存丈钙。
1.UIImageView + WebCache imageView的圖片
2.UIButton + WebCache 給按鈕設置圖片
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)存圖片緩存,應用結(jié)束的時候清理過期圖片蹂风,
當應用進入后臺時吗伤,會涉及到『Long-Running Task』正常程序在進入后臺后、雖然可以繼續(xù)執(zhí)行任務硫眨。但是在時間很短內(nèi)就會被掛起待機足淆。
Long-Running可以讓系統(tǒng)為app再多分配一些時間來處理一些耗時任務。

流程圖如下


10405915-153e48f83e03898c.png

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

自動刪除機制:當系統(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ā)者通過代理處理完圖片并返回后緩存门怪。二是當緩存中沒有、但是從硬盤中查詢到了圖片锅纺。

4.磁盤的緩存時長默認是一周掷空,清除時間是當程序退出到后臺、或者被殺死的時候囤锉,在后臺執(zhí)行耗時任務是要申請時間坦弟,不要一進入后臺短時間就被掛起

// 默認緩存時長
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設置最大顯示的代碼沧侥,所以猜測默認沒有設置上限

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

5.如何保證內(nèi)存和磁盤的讀寫安全可霎?
a:NScache是線程安全的,在多線程操作中宴杀,不需要對Cache加鎖癣朗。
讀取緩存的時候是在主線程進行。所有不需要要擔心線程安全
b:磁盤的讀取雖然創(chuàng)建了一個NSOperation對象旺罢、但據(jù)我所見這個對象只是用來標記該操作是否被取消旷余、以及取消之后不再讀取磁盤文件的作用绢记。
真正的磁盤緩存是在另一個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.磁盤的路徑:默認路徑
緩存在磁盤沙盒目錄下Library/Caches
二級目錄為~/Library/Caches/default/com.hackemist.SDWebImageCache.default
當然也可自定義文件名

  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ù)昌执,而這個解碼操作比較耗時。
你也可以這么理解诈泼,圖片在遠端存儲一定都是編碼后存儲的,這樣體積小煤禽,一個圖像可以看做是一個圖像文件铐达,里面包含了文件頭,文件體和文件尾檬果,圖像的數(shù)據(jù)就包含在文件體中瓮孙,而我們的解碼就是運用算法將文件體中的圖像數(shù)據(jù)轉(zhuǎn)化為位圖數(shù)據(jù),方便渲染和展示选脊。
iOS默認是在主線程解碼杭抠,所以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ù)源開啟一個下載任務并返回一個image,當cell重用時啼肩,其數(shù)據(jù)源又會開啟一個下載任務下載新的image,但關(guān)聯(lián)的對象是同一個imageView橄妆,這個時候直接setImage時會發(fā)生錯亂。

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

[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)系作者
  • 序言:七十年代末斟或,一起剝皮案震驚了整個濱河市素征,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖稚茅,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纸淮,死亡現(xiàn)場離奇詭異,居然都是意外死亡亚享,警方通過查閱死者的電腦和手機咽块,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來欺税,“玉大人侈沪,你說我怎么就攤上這事⊥碓洌” “怎么了亭罪?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長歼秽。 經(jīng)常有香客問我应役,道長,這世上最難降的妖魔是什么燥筷? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任箩祥,我火速辦了婚禮,結(jié)果婚禮上肆氓,老公的妹妹穿的比我還像新娘袍祖。我一直安慰自己,他們只是感情好谢揪,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布蕉陋。 她就那樣靜靜地躺著,像睡著了一般拨扶。 火紅的嫁衣襯著肌膚如雪凳鬓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天患民,我揣著相機與錄音村视,去河邊找鬼。 笑死酒奶,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的奶赔。 我是一名探鬼主播惋嚎,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼站刑!你這毒婦竟也來了另伍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摆尝,沒想到半個月后温艇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡堕汞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年勺爱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讯检。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡琐鲁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出人灼,到底是詐尸還是另有隱情围段,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布投放,位于F島的核電站奈泪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灸芳。R本人自食惡果不足惜涝桅,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耗绿。 院中可真熱鬧苹支,春花似錦、人聲如沸误阻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽究反。三九已至寻定,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間精耐,已是汗流浹背狼速。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卦停,地道東北人向胡。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像惊完,于是被迫代替她去往敵國和親僵芹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容