SDWebImage 常用于對(duì)圖像的下載及緩存等。作者Olivier Poitrey蜻展,法國(guó)視頻分享網(wǎng)站Dailymotion(后被法國(guó)電信運(yùn)營(yíng)商O(píng)range收購(gòu)焊唬,被和諧) 的 CTO媒惕,擁有多個(gè)開(kāi)源項(xiàng)目拼苍。目前SDWebImage獲得巨大成功贬芥,用戶甚眾吐辙,并且在github上已獲取超過(guò)17.7k star。
和筆者另一篇文章 **iOS 李明杰 MJRefresh源碼解析 **類似蘸劈,本文主要素材來(lái)源有正在學(xué)hybrid開(kāi)發(fā)的iOS開(kāi)發(fā)者 J_Knight 的文章昏苏、楊千嬅染了紅頭發(fā) 的博客一行行看SDWebImage源碼(一)、一行行看SDWebImage源碼(二) 和 github上作者的用法介紹威沫,再一次表示敬意贤惯。
本文分成兩個(gè)部分對(duì)SDWebImage進(jìn)行介紹,上部分從框架層次方面進(jìn)行概述棒掠,主要是筆者在學(xué)習(xí)使用和研究過(guò)程中的心得感悟孵构;下半部分則是對(duì)具體各個(gè)類實(shí)現(xiàn)代碼的詳細(xì)解析。因此筆者建議烟很,若是已經(jīng)對(duì)SDWebImage有一定了解的同學(xué)可以看一下上部分颈墅,若是剛開(kāi)始使用此框架的同學(xué)大可略過(guò)第一部分蜡镶,直接從代碼實(shí)現(xiàn)看起,以免被筆者可能不太成熟的見(jiàn)解所誤導(dǎo)恤筛。水平有限官还,還望大家不吝賜教。
第一部分
架構(gòu)設(shè)計(jì)(干貨)
"捫心自問(wèn)"叹俏,假如讓我們自己設(shè)計(jì)一個(gè)具有圖片異步加載并緩存功能的框架妻枕,我們?cè)撛趺丛O(shè)計(jì)僻族。冥想3分鐘粘驰,好好看看這張類圖,好好看看這張類圖述么,好好看看這張類圖蝌数,在這里就先根據(jù)源碼試著反推整個(gè)框架的設(shè)計(jì)思路。
1. 封裝度秘、繼承顶伞、多態(tài)特性
- 毫無(wú)疑問(wèn),封裝是單一職責(zé)原則的題中之義剑梳。
SDWebImage
根據(jù)不同功能進(jìn)行了不同的類的封裝:
類 | 職責(zé) |
---|---|
UIView+WebCache |
各種視圖的基類唆貌,容納圖像的容器,操作綁定的對(duì)象 |
SDImageCache垢乙、SDImageCacheConfig |
負(fù)責(zé)處理圖像緩存锨咙、配置緩存參數(shù) |
SDWebImageDownloader |
核心代碼,負(fù)責(zé)圖像的異步下載 |
**SDWebImageManager ** |
綁定顯示圖像的視圖追逮、圖像下載酪刀、圖像緩存3者的管理類,是操作的中樞 |
SDWebImagePrefetcher |
預(yù)加載 |
SDWebImageDecoder |
圖像解壓縮 |
- 面向接口編碼钮孵,而非面向?qū)崿F(xiàn)編碼骂倘,繼承中的依賴倒置原則,即基類中定義接口巴席,子類中對(duì)基類接口做具體實(shí)現(xiàn)历涝。例如
SDWebImage
為UIImageView、UIButton
等視圖控件提供圖像或Gif加載功能漾唉,所以在共同基類UIView
中提供圖像異步下載等功能荧库。
2. 設(shè)計(jì)模式
-
裝飾模式
裝飾模式,動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)毡证。就擴(kuò)展功能來(lái)說(shuō)电爹,裝飾模式相比生成子類更為靈活(四人幫 1994)。 而類目就是OC語(yǔ)言框架對(duì)裝飾模式的經(jīng)典應(yīng)用料睛, SDWebImage
作者通過(guò)類目的方式靈活的給視圖類添加了各種功能丐箩,同時(shí)也完成了下載摇邦、存儲(chǔ)功能與具體視圖的綁定,值得我輩學(xué)習(xí)屎勘。
-
單例模式
** 單例模式:保證一個(gè)類僅有一個(gè)實(shí)例施籍,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)(四人幫 1994)。** 老生常談的設(shè)計(jì)模式概漱,亦是經(jīng)典的設(shè)計(jì)模式丑慎。SDWebImage
框架中對(duì)其有大量應(yīng)用。
單例類 | 方法 | 職責(zé) |
---|---|---|
SDImageCache |
sharedImageCache |
負(fù)責(zé)處理圖像緩存瓤摧、配置緩存參數(shù) |
SDWebImageDownloader |
sharedDownloader |
核心代碼竿裂,負(fù)責(zé)圖像的異步下載 |
SDWebImageManager |
sharedManager |
綁定視圖、下載照弥、緩存的管理類腻异,操作的中樞 |
SDWebImagePrefetcher |
sharedImagePrefetcher |
預(yù)加載 |
-
適配器模式、代理模式
** 適配器模式:將一個(gè)類的接口換成客戶希望的另一個(gè)接口这揣。使得原本由于接口不兼容而不能一起工作的那些類可以一起工作(四人幫 1994)悔常。** 若不明白直接說(shuō)協(xié)議--委托就是屬于對(duì)象適配器就明白了,也對(duì)那句“OC中的多繼承通過(guò)協(xié)議實(shí)現(xiàn)的”有更深的理解吧给赞。在此不再詳敘机打,有興趣的同學(xué)可以看看四人幫的《設(shè)計(jì)模式》一書(shū)。
協(xié)議類 | 職責(zé) |
---|---|
SDWebImageOperation |
提供取消操作的兼容接口cancel
|
SDWebImageDownloaderOperationInterface |
自定義下載操作時(shí)使用 |
SDWebImageManagerDelegate |
提供管理類的回調(diào) |
SDWebImagePrefetcherDelegate |
提供預(yù)加載回調(diào) |
-
生成器模式
生成器模式:將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表現(xiàn)分離片迅,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表現(xiàn)残邀。 這也是筆者要著重推薦的一種設(shè)計(jì)模式,靈活運(yùn)用能很好的達(dá)到視圖與功能分離的解耦目的障涯,我們編碼中經(jīng)彻奁欤可見(jiàn)的各種Manager,包括SDWebImage
中的SDWebImageManager唯蝶、SDWebImageDownloader
等九秀,根據(jù)不同參數(shù)對(duì)視圖表現(xiàn)或功能進(jìn)行比較復(fù)雜的配置、操作粘我,使用此方法可以很好地進(jìn)行代碼分層鼓蜒、解耦,使邏輯清晰征字。
3. 為什么要進(jìn)行圖像解壓縮
/* UIImage *image = [UIImage sd_imageWithData:data];
image = [UIImage decodedImageWithImage:image];
*/
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image;
SDWebImageDecoder
代碼中顯示是從UIImage
返回UIImage
都弹,自己返回自己,何苦來(lái)哉匙姜?
請(qǐng)注意畅厢,這里有2個(gè)步驟.
-
NSData
-->UIImage
-
UIImage
-->UIImage
解壓已經(jīng)下載緩存起來(lái)的圖片可以提高性能,但是會(huì)消耗大量的內(nèi)存。
具體原理有興趣的同學(xué)可參考以下文章:
SDWebImageDecoder氮昧,異步對(duì)圖像進(jìn)行了一次解壓??
談?wù)?iOS 中圖片的解壓縮
4. 圖像加載及緩存邏輯
一般來(lái)講緩存分為兩種:
- 永久存儲(chǔ)框杜,又叫磁盤(pán)存儲(chǔ)浦楣,在iOS 中即存儲(chǔ)在沙盒
- 內(nèi)存存儲(chǔ),即存儲(chǔ)在進(jìn)程分配的內(nèi)存咪辱,程序關(guān)閉后及消失振劳。經(jīng)常的做法是聲明一全局變量來(lái)賦值進(jìn)去(iOS 常用
NSDictionary
/NSCache
),SDWebImage
用的是NSCache
NSDictionary
和NSCache
區(qū)別可看下篇文章:
構(gòu)建緩存時(shí)選用NSCache而非NSDictionary
-
圖像緩存
- 驗(yàn)證此URL沒(méi)有(是否)被標(biāo)記為不可用
在
SDImageCache
里查詢沒(méi)有(是否)存在緩存的圖片
- 查看內(nèi)存的緩存,根據(jù)
key
從NSCache
獲取Value
- 查看磁盤(pán)的緩存油狂,若存在計(jì)算緩存代價(jià)若允許存入內(nèi)存历恐,拋入主線程
或
用戶(是否)通過(guò)參數(shù)
SDWebImageOptions
要求必須網(wǎng)絡(luò)刷新
3.用戶(是否)通過(guò)委托設(shè)置允許對(duì)此URL進(jìn)行網(wǎng)絡(luò)下載
-
圖像下載
- 創(chuàng)建下載請(qǐng)求
- 創(chuàng)建下載操作
- url證書(shū)
- 優(yōu)先級(jí)
- 在下載隊(duì)列里添加下載操作,執(zhí)行下載操作
5. 用戶認(rèn)證 NSURLCredential
當(dāng)連接客戶端與服務(wù)端進(jìn)行數(shù)據(jù)傳輸?shù)臅r(shí)候,web服務(wù)器收到客戶端請(qǐng)求時(shí)可能需要先驗(yàn)證客戶端是否是正常用戶,再?zèng)Q定是否返回該接口的真實(shí)數(shù)據(jù)专筷。
iOS7.0之前使用的網(wǎng)絡(luò)框架是
NSURLConnection
,在 2013 的 WWDC 上弱贼,蘋(píng)果推出了NSURLConnection
的繼任者:NSURLSession
,SDWebImage
使用的是NSURLConnection
,這兩種網(wǎng)絡(luò)框架的認(rèn)證調(diào)用的方法也是不一樣的,有興趣的可以去google一下這里只看下NSURLConnection
的認(rèn)證
認(rèn)證過(guò)程:
1. web服務(wù)器接收到來(lái)自客戶端的請(qǐng)求
2. web服務(wù)并不直接返回?cái)?shù)據(jù),而是要求客戶端提供認(rèn)證信息,也就是說(shuō)挑戰(zhàn)是服務(wù)端向客戶端發(fā)起的
2.1 要求客戶端提供用戶名與密碼挑戰(zhàn)NSInternetPassword
2.2 要求客戶端提供客戶端證書(shū) NSClientCertificate
2.3 要求客戶端信任該服務(wù)器
3. 客戶端回調(diào)執(zhí)行,接收到需要提供認(rèn)證信息,然后提供認(rèn)證信息,并再次發(fā)送給web服務(wù)
4. web服務(wù)驗(yàn)證認(rèn)證信息
4.1 認(rèn)證成功,將最終的數(shù)據(jù)結(jié)果發(fā)送給客戶端
4.2 認(rèn)證失敗,錯(cuò)誤此次請(qǐng)求,返回錯(cuò)誤碼401
6. 下載操作的 任務(wù)調(diào)度和多線程安全問(wèn)題
dispatch_barrier_sync
是SD選用的GCD函數(shù),self.barrierQueue
是存放任務(wù)的隊(duì)列,block里面是要執(zhí)行的任務(wù)仁堪。
SD添加下載任務(wù)是同步的哮洽,而且都是在self.barrierQueue
這個(gè)并行隊(duì)列中填渠,同步添加任務(wù)弦聂。這樣也保證了根據(jù)executionOrder
設(shè)置依賴關(guān)是正確的。
換句話說(shuō)如果創(chuàng)建下載任務(wù)不是使用dispatch_barrier_sync
完成的氛什,而是使用異步方法 莺葫,雖然依次添加創(chuàng)建下載操作A、B枪眉、C的任務(wù)捺檬,但實(shí)際創(chuàng)建順序可能為A、C贸铜、B堡纬,這樣當(dāng)executionOrder
的值是SDWebImageDownloaderLIFOExecutionOrder
,設(shè)置的操作依賴關(guān)系就變成了A依賴C蒿秦,C依賴B
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
//先進(jìn)先出烤镐, 默認(rèn)值,所有的下載操作以隊(duì)列類型執(zhí)行,先被加入下載隊(duì)列的操作先執(zhí)行
SDWebImageDownloaderFIFOExecutionOrder,
// 先進(jìn)后出棍鳖,所有的下載操作以棧類型執(zhí)行,后被加入下載隊(duì)列的操作先執(zhí)行
SDWebImageDownloaderLIFOExecutionOrder
};
有興趣的同學(xué)可以看看:
通過(guò)GCD中的dispatch_barrier_(a)sync加強(qiáng)對(duì)sync中所謂等待的理解
dispatch_barrier_sync
VS dispatch_barrier_sync
** Dispatch Barrier解決多線程并發(fā)讀寫(xiě)一個(gè)資源發(fā)生死鎖 **
sync說(shuō)明了這是個(gè)同步函數(shù),任務(wù)不會(huì)立即返回,會(huì)等到任務(wù)執(zhí)行結(jié)束才返回炮叶。
使用dispatch_barrier_sync函數(shù)創(chuàng)建的任務(wù)會(huì)首先查看隊(duì)列是否有別的任務(wù)要執(zhí)行,如果有則等待已有任務(wù)執(zhí)行完畢再執(zhí)行渡处;同時(shí)在此方法后添加的任務(wù)必須等到此方法中的任務(wù)執(zhí)行后才能執(zhí)行镜悉,利用這個(gè)方法可以控制執(zhí)行順序。
Dispatch Barrier確保提交的block是指定隊(duì)列中特定時(shí)段唯一在執(zhí)行的一個(gè)医瘫。在所有先于Dispatch Barrier的任務(wù)都完成的情況下這個(gè)block才開(kāi)始執(zhí)行侣肄。輪到這個(gè)block時(shí)barrier會(huì)執(zhí)行這個(gè)block并且確保隊(duì)列在此過(guò)程 不會(huì)執(zhí)行其他任務(wù)。block完成后才恢復(fù)隊(duì)列醇份。
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
這是用戶自己創(chuàng)建的隊(duì)列稼锅,DISPATCH_QUEUE_CONCURRENT
代表的是它是一個(gè)并行隊(duì)列叮喳。
為什么選擇并發(fā)隊(duì)列而不是串行隊(duì)列?
串行隊(duì)列可以保證任務(wù)按照添加順序挨個(gè)開(kāi)始執(zhí)行,并且上個(gè)任務(wù)結(jié)束才開(kāi)始下一個(gè)任務(wù)缰贝,這已經(jīng)可以保證任務(wù)的執(zhí)行順序(或者說(shuō)是任務(wù)結(jié)束的順序)了馍悟,但是并發(fā)隊(duì)列只能保證任務(wù)的開(kāi)始,不能保證任務(wù)的結(jié)束順序剩晴,解決辦法就是:并發(fā)隊(duì)列使用Barrier保證控制任務(wù)結(jié)束順序锣咒。
這部分就先到這里繼續(xù)向下看:
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
這些代碼的目的都是為了給url綁定回調(diào)
URLCallbacks
是一個(gè)可變字典,key
是NSURL
類型赞弥,value
為NSMutableArray
類型毅整,value
(數(shù)組里面)只包含一個(gè)元素,這個(gè)元素的類型是NSMutableDictionary
類型绽左,這個(gè)字典的key
為NSString
類型代表著回調(diào)類型悼嫉,value
為block
,是對(duì)應(yīng)的回調(diào)
繼續(xù)向下看:
if (first) {
createCallback();
}
如果url第一次綁定它的回調(diào)拼窥,也就是第一次使用這個(gè)url創(chuàng)建下載任務(wù)戏蔑,則執(zhí)行一次創(chuàng)建回調(diào)
如何確保同一url對(duì)應(yīng)的圖片不會(huì)被重復(fù)下載?
在創(chuàng)建回調(diào)中 創(chuàng)建下載操作(下載操作并不是在這里創(chuàng)建的)鲁纠,
dispatch_barrier_sync
執(zhí)行確保同一時(shí)間只有一個(gè)線程操作URLCallbacks
屬性总棵,也就是確保了下面創(chuàng)建過(guò)程中在給operation
傳遞回調(diào)的時(shí)候能取到正確的self.URLCallbacks[url]
值,同時(shí)確保后面有相同的url
再次創(chuàng)建的時(shí)候if (!self.URLCallbacks[url])
分支不再進(jìn)入改含,first==NO
情龄,也就不再繼續(xù)調(diào)用創(chuàng)建回調(diào),這樣就確保了同一個(gè)url
對(duì)應(yīng)的圖片不會(huì)重復(fù)下載
以上這部分代碼總結(jié)起來(lái)只做了一件事情:在barrierQueue隊(duì)列中創(chuàng)建下載任務(wù)
功能
-
UIImageView
捍壤,UIButton
骤视,MKAnnotationView
的類別添加網(wǎng)頁(yè)圖像和緩存管理 - 異步圖像下載器
- 具有自動(dòng)到期處理的異步 內(nèi)存+磁盤(pán) 緩存
- 背景圖像解壓縮
- 同一個(gè)URL不會(huì)下載多次
- 虛假無(wú)效網(wǎng)址不會(huì)重復(fù)請(qǐng)求
- 主線程永遠(yuǎn)不會(huì)被阻塞
- 性能優(yōu)勢(shì)
- 用GCD 和 ARC
優(yōu)勢(shì)
相對(duì)于原生NSURLRequest
的NSURLCache
處理磁盤(pán)緩存,SDWebImage
有什么優(yōu)勢(shì)鹃觉?
從iOS 105.0起专酗,
NSURLCache
在內(nèi)存和磁盤(pán)上緩存的是原始HTTP響應(yīng)數(shù)據(jù),每次擊中緩存時(shí)帜慢,程序都必須將原始緩存數(shù)據(jù)轉(zhuǎn)換為UIImage
才能使用笼裳。而這個(gè)轉(zhuǎn)化過(guò)程涉及復(fù)雜且廣泛的操作,如數(shù)據(jù)解析(解析被編碼過(guò)的HTTP數(shù)據(jù))粱玲,內(nèi)存復(fù)制等躬柬。SDWebImage采用UIImage的形式在內(nèi)存中緩存圖片數(shù)據(jù),并將已解碼過(guò)的HTTP數(shù)據(jù)的壓縮文件存儲(chǔ)在磁盤(pán)上抽减。 使用NSCache將UIImage原樣存儲(chǔ)在內(nèi)存中允青,因此不會(huì)涉及任何副本,并且只要程序或系統(tǒng)需要卵沉,內(nèi)存就會(huì)被釋放颠锉。
一般第一次在
UIImageView
中使用UIImage時(shí)的圖像解壓縮是在主線程完成的法牲,而SDWebImageDecoder強(qiáng)制其在后臺(tái)線程中。SDWebImage
完全繞過(guò)復(fù)雜且經(jīng)常配置錯(cuò)誤的HTTP緩存控制協(xié)議琼掠,大大加速了緩存查找拒垃。
相對(duì)于AFNetworking
為UIImageView
提供的類似功能,SDWebImage
有什么優(yōu)勢(shì)瓷蛙?
-
AFNetworking
默認(rèn)使用NSCache
在UIKit
中對(duì)UIImageView
和UIButton
配置內(nèi)存緩存悼瓮。 -
SDWebImage
同時(shí)利用Foundation
框架中的NSURLCache
做URL
系統(tǒng)加載緩存,也用到了NSCache
艰猬。AFNetworking
還提供了如圖像數(shù)據(jù)的后臺(tái)解壓縮功能横堡。 - 也就是說(shuō)
AFNetworking
UIKit
部分實(shí)現(xiàn)了簡(jiǎn)單的異步圖像加載類別祠墅,是SDWebImage
的部分功能
第二部分
在使用這個(gè)框架的時(shí)候坦袍,只需要提供一個(gè)下載的url和占位圖就可以在回調(diào)里拿到下載后的圖片:
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
imageview.image = image;
NSLog(@"圖片加載完成");
}];
而且我們還可以不設(shè)置占位圖片廊酣,也可以不使用回調(diào)的block苍苞,非常靈
//圖片下載完成后直接顯示下載后的圖片
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"]];
在最開(kāi)始先簡(jiǎn)單介紹這個(gè)框架
這個(gè)框架的核心類是SDWebImageManger
,在外部有UIImageView+WebCache
和 UIButton+WebCache
為下載圖片的操作提供接口熟空。內(nèi)部有SDWebImageManger
負(fù)責(zé)處理和協(xié)調(diào) SDWebImageDownloader
和 SDWebImageCache
:SDWebImageDownloader
負(fù)責(zé)具體的下載任務(wù)昏名,SDWebImageCache
負(fù)責(zé)關(guān)于緩存的工作:添加索抓,刪除碳蛋,查詢緩存胚泌。
首先我們大致看一下這個(gè)框架的調(diào)用流程圖:
從這個(gè)流程圖里可以大致看出,該框架分為兩個(gè)層:UIKit層(負(fù)責(zé)接收下載參數(shù))和工具層(負(fù)責(zé)下載操作和緩存)肃弟。
OK~基本流程大概清楚了,我們看一下每個(gè)層具體實(shí)現(xiàn)吧~
UIKit層
該框架最外層的類是UIImageView +WebCache
零蓉,我們將圖片的URL笤受,占位圖片直接給這個(gè)類。下面是這個(gè)類的公共接口:
// ============== UIImageView + WebCache.h ============== //
- (void)sd_setImageWithURL:(NSURL *)url;
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder;
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(NSURL *)url
completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock;
可以看出敌蜂,這個(gè)類提供的接口非常靈活箩兽,可以根據(jù)我們自己的需求來(lái)調(diào)用其中某一個(gè)方法,而這些方法到最后都會(huì)走到:
// ============== UIImageView + WebCache.m ============== //
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionBlock)completedBlock;
而這個(gè)方法里面章喉,調(diào)用的是UIView+WebCache
分類的:
// ============== UIView+ WebCache.m ============== //
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
為什么不是
UIImageView+WebCache
而要上一層到UIView
的分類里呢汗贫?
因?yàn)镾DWebImage框架也支持UIButton
的下載圖片等方法,所以需要在它們的父類:UIView
里面統(tǒng)一一個(gè)下載方法秸脱。
簡(jiǎn)單看一下這個(gè)方法的實(shí)現(xiàn)(省略的代碼用...代替):
// ============== UIView+ WebCache.m ============== //
//valid key:UIImageView || UIButton
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//UIView+WebCacheOperation 的 operationDictionary
//下面這行代碼是保證沒(méi)有當(dāng)前正在進(jìn)行的異步下載操作, 使它不會(huì)與即將進(jìn)行的操作發(fā)生沖突
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/* options & SDWebImageDelayPlaceholder這是一個(gè)位運(yùn)算的與操作,
!(options & SDWebImageDelayPlaceholder)的意思就是options參數(shù)
不是SDWebImageDelayPlaceholder,就執(zhí)行以下操作
*/
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
//如果url存在
if (url) {
...
__weak __typeof(self)wself = self;
//SDWebImageManager下載圖片
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
...
//dispatch_main_sync_safe : 保證block能在主線程進(jìn)行
dispatch_main_async_safe(^{
if (!sself) {
return;
}
/* SDWebImageAvoidAutoSetImage,默認(rèn)情況下圖片會(huì)在下載完畢后自動(dòng)添加
給imageView,但是有些時(shí)候我們想在設(shè)置圖片之前加一些圖片的處理,就要下
載成功后去手動(dòng)設(shè)置圖片了,不會(huì)執(zhí)行` wself.image = image; `,而是直接執(zhí)行
完成回調(diào)落包,有用戶自己決定如何處理。
*/
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
//image摊唇,而且不自動(dòng)替換 placeholder image
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
//存在image咐蝇,需要馬上替換 placeholder image
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
//沒(méi)有image,在圖片下載完之后顯示 placeholder image
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//在操作緩存字典(operationDictionary)里添加operation巷查,表示當(dāng)前的操作正在進(jìn)行
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
//如果url不存在有序,就在completedBlock里傳入error(url為空)
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
值得一提的是抹腿,在這一層,使用一個(gè)字典operationDictionary專門(mén)用作存儲(chǔ)操作的緩存旭寿,隨時(shí)添加警绩,刪除操作任務(wù)。
而這個(gè)字典是UIView+WebCacheOperation分類的關(guān)聯(lián)對(duì)象盅称,它的存取方法使用運(yùn)行時(shí)來(lái)操作:
// ============== UIView+WebCacheOperation.m ============== //
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
為什么不直接在
UIImageView+WebCache
里直接關(guān)聯(lián)這個(gè)對(duì)象呢房蝉?我覺(jué)得這里作者應(yīng)該是遵從面向?qū)ο蟮膯我宦氊?zé)原則(SRP:Single responsibility principle),就連類都要履行這個(gè)職責(zé)微渠,何況分類呢搭幻?這里作者專門(mén)創(chuàng)造一個(gè)分類UIView+WebCacheOperation
來(lái)管理操作緩存(字典)。
到這里逞盆,UIKit層上面的東西都講完了檀蹋,現(xiàn)在開(kāi)始正式講解工具層。
工具層
上文提到過(guò)云芦,SDWebImageManager
同時(shí)管理SDImageCache
和SDWebImageDownloader
兩個(gè)類俯逾,它是這一層的老大哥。在下載任務(wù)開(kāi)始的時(shí)候舅逸,SDWebImageManager
首先訪問(wèn)SDImageCache
來(lái)查詢是否存在緩存桌肴,如果有緩存,直接返回緩存的圖片琉历。如果沒(méi)有緩存坠七,就命令SDWebImageDownloader
來(lái)下載圖片,下載成功后旗笔,存入緩存彪置,顯示圖片。以上是SDWebImageManager
大致的工作流程蝇恶。
在詳細(xì)講解SDWebImageManager
是如何下載圖片之前拳魁,我們先看一下這個(gè)類的幾個(gè)重要的屬性:
// ============== SDWebImageManager.m ============== //
/*
*初始化方法
*1.獲得一個(gè)SDImageCache的實(shí)例
*2.獲得一個(gè)SDWebImageDownloader的實(shí)例
*3.新建一個(gè)MutableSet來(lái)存儲(chǔ)下載失敗的url
*4.新建一個(gè)用來(lái)存儲(chǔ)下載operation的可變數(shù)組
*/
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;
SDWebImageManager下載圖片的方法只有一個(gè):
[SDWebImageManager.sharedManager loadImageWithURL:options:progress:completed:]
看一下這個(gè)方法的具體實(shí)現(xiàn):
// ============== SDWebImageManager.m ============== //
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
...
//在SDImageCache里查詢是否存在緩存的圖片
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
...
//(沒(méi)有緩存圖片) || (即使有緩存圖片,也需要更新緩存圖片) || (代理沒(méi)有響應(yīng)imageManager:shouldDownloadImageForURL:消息撮弧,默認(rèn)返回yes潘懊,需要下載圖片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下載圖片)
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//1. 存在緩存圖片 && 即使有緩存圖片也要下載更新圖片
if (cachedImage && options & SDWebImageRefreshCached) {
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// 2. 如果不存在緩存圖片
...
//開(kāi)啟下載器下載
//subOperationToken 用來(lái)標(biāo)記當(dāng)前的下載任務(wù)贿衍,便于被取消
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// 1. 如果任務(wù)被取消授舟,則什么都不做,避免和其他的completedBlock重復(fù)
} else if (error) {
//2. 如果有錯(cuò)誤
//2.1 在completedBlock里傳入error
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
//2.2 在錯(cuò)誤url名單中添加當(dāng)前的url
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
//3. 下載成功
//3.1 如果需要下載失敗后重新下載舌厨,則將當(dāng)前url從失敗url名單里移除
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//3.2 進(jìn)行緩存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
//(即使緩存存在岂却,也要刷新圖片) && 緩存圖片 && 不存在下載后的圖片:不做操作
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
//(下載圖片成功 && (沒(méi)有動(dòng)圖||處理動(dòng)圖) && (下載之后,緩存之前處理圖片) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
//緩存圖片
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//將圖片傳入completedBlock
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
//(圖片下載成功并結(jié)束)
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
//如果完成,從當(dāng)前運(yùn)行的操作列表里移除當(dāng)前操作
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//取消的block
operation.cancelBlock = ^{
//取消當(dāng)前的token
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
//從當(dāng)前運(yùn)行的操作列表里移除當(dāng)前操作
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
//存在緩存圖片
__strong __typeof(weakOperation) strongOperation = weakOperation;
//調(diào)用完成的block
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
//刪去當(dāng)前的的下載操作(線程安全)
[self safelyRemoveOperationFromRunning:operation];
} else {
//沒(méi)有緩存的圖片躏哩,而且下載被代理終止了
__strong __typeof(weakOperation) strongOperation = weakOperation;
// 調(diào)用完成的block
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
//刪去當(dāng)前的下載操作
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
看完了SDWebImageManager
的回調(diào)處理署浩,我們分別看一下
SDImageCache
和SDWebImageDownloader
內(nèi)部具體是如何工作的。首先看一下SDImageCache
:
SDImageCache
屬性
// ============== SDImageCache.m ============== //
@property (strong, nonatomic, nonnull) NSCache *memCache;//內(nèi)存緩存
@property (strong, nonatomic, nonnull) NSString *diskCachePath;//磁盤(pán)緩存路徑
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;//
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t //ioQueue唯一子線程;
核心方法:查詢緩存
// ============== SDImageCache.m ============== //
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
//================查看內(nèi)存的緩存=================//
UIImage *image = [self imageFromMemoryCacheForKey:key];
// 如果存在扫尺,直接調(diào)用block筋栋,將image,data正驻,CaheType傳進(jìn)去
if (image) {
NSData *diskData = nil;
// 如果是gif弊攘,就拿到data,后面要傳到doneBlock里姑曙。不是gif就傳nil
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
// 因?yàn)閳D片有緩存可供使用襟交,所以不用實(shí)例化NSOperation,直接范圍nil
return nil;
}
//================查看磁盤(pán)的緩存=================//
NSOperation *operation = [NSOperation new];
//唯一的子線程:self.ioQueue
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// 在用之前就判斷operation是否被取消了伤靠,作者考慮的非常嚴(yán)謹(jǐn)
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];c
if (diskImage && self.config.shouldCacheImagesInMemory) {
// cost 被用來(lái)計(jì)算緩存中所有對(duì)象的代價(jià)捣域。當(dāng)內(nèi)存受限或者所有緩存對(duì)象的總代價(jià)超過(guò)了最大允許的值時(shí),緩存會(huì)移除其中的一些對(duì)象宴合。
NSUInteger cost = SDCacheCostForImage(diskImage);
// 存入內(nèi)存緩存中
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
SDWebImageDownloader
屬性
// ============== SDWebImageDownloader.m ============== //
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;//下載隊(duì)列
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;//最后添加的下載操作
@property (assign, nonatomic, nullable) Class operationClass;//操作類
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;//操作數(shù)組
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;//HTTP請(qǐng)求頭
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;//用來(lái)阻塞前面的下載線程(串行化)
核心方法:下載圖片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
// 為防止重復(fù)緩存焕梅,默認(rèn)網(wǎng)絡(luò)請(qǐng)求不進(jìn)行緩存操作
// 創(chuàng)建下載請(qǐng)求,配置相關(guān)參數(shù)
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
// 針對(duì)每次下載操作卦洽,SD自創(chuàng)一個(gè)操作類贞言,眾多操作放在一個(gè)操作隊(duì)列中,便于管理眾多下載操作
// 創(chuàng)建下載操作:SDWebImageDownloaderOperation用于請(qǐng)求網(wǎng)絡(luò)資源的操作阀蒂,它是一個(gè) NSOperation 的子類
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
//url證書(shū)
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
// 優(yōu)先級(jí)
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
// 在下載隊(duì)列里添加下載操作该窗,執(zhí)行下載操作
[sself.downloadQueue addOperation:operation];
// 如果后進(jìn)先出
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
// addDependency:參數(shù)opertaion倍添加到NSOperationQueue后,只有等該opertion結(jié)束后才能執(zhí)行其他的operation脂新,實(shí)現(xiàn)了后進(jìn)先出
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
這里面還有一個(gè)addProgressCallback: progressBlock: completedBlock: forURL: createCallback:
方法挪捕,用來(lái)保存progressBlock
和completedBlock
。我們看一下這個(gè)方法的實(shí)現(xiàn):
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil.
If it is nil immediately call the completed block with no image or data.
// URL 將會(huì)做回調(diào)字典的key争便,不能為nil
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
__block SDWebImageDownloadToken *token = nil;
// 串行化前面所有的操作
dispatch_barrier_sync(self.barrierQueue, ^{
// 當(dāng)前下載操作中取出SDWebImageDownloaderOperation實(shí)例
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
// 如果沒(méi)有,就初始化它
if (!operation) {
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
// 這里 downloadOperationCancelToken 默認(rèn)是一個(gè)字典断医,存放 progressBlock 和 completedBlock
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
token = [SDWebImageDownloadToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
這里真正保存兩個(gè)block的方法是addHandlersForProgress: completed:
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 實(shí)例化一個(gè)SDCallbacksDictionary滞乙,存放一個(gè)progressBlock 和 completedBlock
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
// 添加到緩存中 self.callbackBlocks
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
到這里SDWebImage
的核心方法都講解完畢了,其他沒(méi)有講到的部分以后會(huì)慢慢添加上去鉴嗤。
最后看一下一些比較零散的知識(shí)點(diǎn):
1. 運(yùn)行時(shí)存取關(guān)聯(lián)對(duì)象:
存:
// 將operations對(duì)象關(guān)聯(lián)給self斩启,地址為&loadOperationKey,語(yǔ)義是OBJC_ASSOCIATION_RETAIN_NONATOMIC醉锅。
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
韧么亍:
// 將operations對(duì)象通過(guò)地址&loadOperationKey從self里取出來(lái)
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
2. 數(shù)組的寫(xiě)操作需要加鎖(多線程訪問(wèn),避免覆寫(xiě))
//給self.runningOperations加鎖
//self.runningOperations數(shù)組的添加操作
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//self.runningOperations數(shù)組的刪除操作
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
@synchronized (self.runningOperations) {
if (operation) {
[self.runningOperations removeObject:operation];
}
}
}
3. 確保在主線程的宏:
dispatch_main_async_safe(^{
//將下面這段代碼放在主線程中
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
//宏定義:
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
4. 設(shè)置不能為nil的參數(shù)
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
如果在參數(shù)里添加了nonnull關(guān)鍵字,那么編譯器就可以檢查傳入的參數(shù)是否為nil垄琐,如果是边酒,則編譯器會(huì)有警告
5. 容錯(cuò),強(qiáng)制轉(zhuǎn)換類型
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
在傳入的參數(shù)為NSString時(shí)(但是方法參數(shù)要求是NSURL)狸窘,自動(dòng)轉(zhuǎn)換為NSURL