IOS基礎(chǔ)知識-SDWebImage原理篇

SDWebImage內(nèi)部實現(xiàn)過程

1.入口setImageWithURL:placeholderImage:options: 會先把placeholderImage 顯示盅粪,然后 SDWebImageManager 根據(jù) URL 開始處理圖片钓葫。
2.進(jìn)入 SDWebImageManager-downloadWithURL:delegate:options:userInfo: 交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載queryDiskCacheForKey:delegate:userInfo:
3.先從內(nèi)存圖片緩存查找是否有圖片,如果內(nèi)存中已經(jīng)有圖片緩存票顾,SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager础浮。
4.SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到UIImageView+WebCache 等前端展示圖片。
5.如果內(nèi)存緩存中沒有奠骄,生成 NSInvocationOperation 添加到隊列開始從硬盤查找圖片是否已經(jīng)緩存豆同。
6.根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進(jìn)行的操作含鳞,所以回主線程進(jìn)行結(jié)果回調(diào)notifyDelegate:
7.如果上一操作從硬盤讀取到了圖片影锈,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小,會先清空內(nèi)存緩存)蝉绷。SDImageCacheDelegate 回調(diào)imageCache:didFindImage:forKey:userInfo: 進(jìn)而回調(diào)展示圖片鸭廷。
8.如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片熔吗,需要下載圖片辆床,回調(diào) imageCache:didNotFindImageForKey:userInfo:
9.共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
10.圖片下載由 NSURLConnection 來做桅狠,實現(xiàn)相關(guān) delegate 來判斷圖片下載中讼载、下載完成和下載失敗宵晚。
11.connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進(jìn)度加載效果。
12.connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理维雇。
13.圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI晒他。如果有需要對下載的圖片進(jìn)行二次處理吱型,最好也在這里完成,效率會好很多陨仅。
14.在主線程notifyDelegateOnMainThreadWithInfo: 宣告解碼完成津滞,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader。
15.imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成灼伤。
16.通知所有的 downloadDelegates 下載完成触徐,回調(diào)給需要的地方展示圖片。
17.將圖片保存到 SDImageCache 中狐赡,內(nèi)存緩存和硬盤緩存同時保存撞鹉。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程颖侄。
18.SDImageCache 在初始化的時候會注冊一些消息通知鸟雏,在內(nèi)存警告或退到后臺的時候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時候清理過期圖片览祖。
19.SDWebImage 也提供了UIButton+WebCache 和 MKAnnotationView+WebCache孝鹊,方便使用。
20.SDWebImagePrefetcher 可以預(yù)先下載圖片展蒂,方便后續(xù)使用又活。
3788243-cf9c367301b36091.png

3788243-e539ccd72dc07274.png

SDWebImage的緩存策略

基本結(jié)構(gòu)

閑言少敘,咱們這就開始锰悼。 首先咱們來看看 SDWebImage 的整體結(jié)構(gòu):

image

有一個專門的 Cache 分類用來處理圖片的緩存柳骄。 這里面也有兩個類 SDImageCache 和 SDImageCacheConfig。 大部分的緩存處理都在 SDImageCache 這個類中實現(xiàn)松捉。其他幾個文件夾咱們分別有個字的功能夹界,因為咱們這次專門討論緩存策略,所以其他內(nèi)容暫時略過隘世。

Memory 和 Disk 雙緩存

首先可柿,SDWebImage 的圖片緩存采用的是 Memory 和 Disk 雙重 Cache 機制, 聽起來挺高大上吧丙者。其實也不復(fù)雜复斥。

我們先來看看 Memory Cache,貼一段 SDImageCache 的代碼:

@interface SDImageCache ()

#pragma mark - Properties

@property (strong, nonatomic, nonnull) NSCache *memCache;

這里我們發(fā)現(xiàn)械媒, 有一個叫做 memCache 的屬性目锭,它是一個 NSCache 對象评汰,用于實現(xiàn)我們對圖片的 Memory Cache。 SDWebImage 還專門實現(xiàn)了一個叫做 AutoPurgeCache 的類痢虹, 相比于普通的 NSCache被去, 它提供了一個在內(nèi)存緊張時候釋放緩存的能力:

@interface AutoPurgeCache : NSCache

@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;

}

其實就是接受系統(tǒng)的內(nèi)存警告通知,然后清除掉自身的圖片緩存奖唯。 這里大家比較少見的一個類應(yīng)該是 NSCache 了惨缆。 簡單來說,它是一個類似于 NSDictionary 的集合類丰捷,用于在內(nèi)存中存儲我們要緩存的數(shù)據(jù)坯墨。詳細(xì)信息大家可以參考官方文檔:https://developer.apple.com/reference/foundation/nscache

說完 Memory Cache病往, 我們再來說說 Disk Cache捣染,也就是文件緩存。

SDWebImage 會將圖片存放到 NSCachesDirectory 目錄中:

- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {

NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

return [paths[0] stringByAppendingPathComponent:fullNamespace];

}

然后為每一個緩存文件生成一個 md5 文件名, 存放到文件中停巷。

整體機制

為了節(jié)約篇幅耍攘,提升大家的閱讀體驗,這里盡量少貼大段代碼畔勤。 我們前面介紹了 SDWebImage 同時使用內(nèi)存和硬盤兩種緩存少漆。 那么我們來看看當(dāng)使用 SDWebImage 讀取圖片時候的完整流程。 我們一般會使用 SDWebImage 對 UIKit 的擴展硼被,直接加載圖片:

[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcode.jpg"]];

首先這個 Category 方法 sd_setImageWithURL 內(nèi)部會調(diào)用 SDWebImageManager 的 downloadImageWithURL 方法來處理這個圖片 URL:

id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

}];

SDWebImageManager 內(nèi)部的 downloadImageWithURL 方法會先使用我們前面提到的 SDImageCache 類的 queryDiskCacheForKey 方法示损,查詢圖片緩存:

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {

}];

再來看 queryDiskCacheForKey 方法內(nèi)部, 先會查詢 Memory Cache :

UIImage *image = [self imageFromMemoryCacheForKey:key];

if (image) {

doneBlock(image, SDImageCacheTypeMemory);

return nil;

}

如果 Memory Cache 查找不到嚷硫, 就會查詢 Disk Cache:

dispatch_async(self.ioQueue, ^{

if (operation.isCancelled) {

return;

}

@autoreleasepool {

UIImage *diskImage = [self diskImageForKey:key];

if (diskImage && self.shouldCacheImagesInMemory) {

NSUInteger cost = SDCacheCostForImage(diskImage);

[self.memCache setObject:diskImage forKey:key cost:cost];

}

dispatch_async(dispatch_get_main_queue(), ^{

doneBlock(diskImage, SDImageCacheTypeDisk);

});

}

});

查詢 Disk Cache 的時候有一個小插曲检访,就是如果 Disk Cache 查詢成功,還會把得到的圖片再次設(shè)置到 Memory Cache 中仔掸。 這樣做可以最大化那些高頻率展現(xiàn)圖片的效率脆贵。

如果緩存查詢成功, 那么就會直接返回緩存數(shù)據(jù)起暮。 如果不成功卖氨,接下來就開始請求網(wǎng)絡(luò):

id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {

}

請求網(wǎng)絡(luò)使用的是 imageDownloader 屬性,這個示例專門負(fù)責(zé)下載圖片數(shù)據(jù)负懦。 如果下載失敗筒捺, 會把失敗的圖片地址寫入 failedURLs 集合:

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];

}

}

為什么要有這個 failedURLs 呢, 因為 SDWebImage 默認(rèn)會有一個對上次加載失敗的圖片拒絕再次加載的機制。 也就是說,一張圖片在本次會話加載失敗了谬擦,如果再次加載就會直接拒絕。SDWebImage 這樣做可能是為了提高性能吧肯尺。這個機制可能會容易被大家忽略沃缘,所以這里特意提一下,說不定哪天遇到一些奇怪問題時候则吟,這個知識點會幫你快速定位問題~

如果下載圖片成功了槐臀,接下來就會使用 [self.imageCache storeImage] 方法將它寫入緩存,并且調(diào)用 completedBlock 告訴前端顯示圖片:

if (downloadedImage && finished) {

[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];

}

dispatch_main_sync_safe(^{

if (strongOperation && !strongOperation.isCancelled) {

completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);

}

});

好了氓仲,到此為止 SDWebImage 的整體圖片加載流程就都走完了峰档。 由于要控制篇幅,我這里只挑了最重點的幾個節(jié)點寫出來寨昙,SDWebImage 的完整機制肯定會更全面,先幫大家疏通思路掀亩。

是否要重試失敗的 URL

SDWebImage 的整體圖片處理流程咱們體驗了一遍舔哪。 那么有哪些重要的點對咱們使用它會有幫助呢? 我?guī)痛蠹艺砹艘恍?/p>

你可以在加載圖片的時候設(shè)置 SDWebImageRetryFailed 標(biāo)記槽棍,這樣 SDWebImage 就會加載之前失敗過的圖片了捉蚤。 記得我們前面提到的 failedURLs 屬性了吧,這個屬性是在內(nèi)存中存儲的炼七,如果圖片加載失敗缆巧, SDWebImage 會在本次 APP 會話中都不再重試這張圖片了。當(dāng)然這個加載失敗是有條件的豌拙,如果是超時失敗陕悬,不記在內(nèi)。

總之按傅,如果你更需要圖片的可用性捉超,而不是這一點點的性能優(yōu)化,那么你就可以帶上 SDWebImageRetryFailed 標(biāo)記:

[_image sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcodexx.jpg"] placeholderImage:nil options:SDWebImageRetryFailed];

Disk 緩存清理策略

SDWebImage 會在每次 APP 結(jié)束的時候執(zhí)行清理任務(wù)唯绍。 清理緩存的規(guī)則分兩步進(jìn)行拼岳。 第一步先清除掉過期的緩存文件。 如果清除掉過期的緩存之后况芒,空間還不夠惜纸。 那么就繼續(xù)按文件時間從早到晚排序,先清除最早的緩存文件绝骚,直到剩余空間達(dá)到要求耐版。

具體點,SDWebImage 是怎么控制哪些緩存過期压汪,以及剩余空間多少才夠呢椭更? 通過兩個屬性:

@interface SDImageCache : NSObject

@property (assign, nonatomic) NSInteger maxCacheAge;

@property (assign, nonatomic) NSUInteger maxCacheSize;

maxCacheAge 是文件緩存的時長, SDWebImage 會注冊兩個通知:

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(cleanDisk)

name:UIApplicationWillTerminateNotification

object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(backgroundCleanDisk)

name:UIApplicationDidEnterBackgroundNotification

object:nil];

分別在應(yīng)用進(jìn)入后臺和結(jié)束的時候蛾魄,遍歷所有的緩存文件虑瀑,如果緩存文件超過 maxCacheAge 中指定的時長湿滓,就會被刪除掉。

同樣的舌狗, maxCacheSize 控制 SDImageCache 所允許的最大緩存空間叽奥。 如果清理完過期文件后緩存空間依然沒達(dá)到 maxCacheSize 的要求, 那么就會繼續(xù)清理舊文件痛侍,直到緩存空間達(dá)到要求為止朝氓。

了解了這個機制對我們有什么幫助呢? 我們來繼續(xù)講解,我們平時在使用 SDWebImage 的時候是沒接觸過它們的主届。那么以此推理赵哲,它們一定有默認(rèn)值,也確實有:

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

上面是 maxCacheAge 的默認(rèn)值君丁,注釋上寫的很清楚枫夺,緩存一周。 再來看看 maxCacheSize绘闷。 翻了一遍 SDWebImage 的代碼橡庞,并沒有對 maxCacheSize 設(shè)置默認(rèn)值。 這就意味著 SDWebImage 在默認(rèn)情況下不會對緩存空間設(shè)限制印蔗。

這一點可以在 SDWebImage 清理緩存的代碼中求證:

if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {

//清理緩存代碼

}

說明一下扒最, 上面代碼中的 currentCacheSize 變量代表當(dāng)前圖片緩存占用的空間。 從這里可以看出华嘹, 只有在 maxCacheSize 大于 0 并且當(dāng)前緩存空間大于 maxCacheSize 的時候才進(jìn)行第二步的緩存清理吧趣。

這意味著什么呢? 其實就是 SDWebImage 在默認(rèn)情況下是不對我們的緩存大小設(shè)限制的耙厚,理論上再菊,APP 中的圖片緩存可以占滿整個設(shè)備。

SDWebImage 的這個特性還是比較容易被大家忽略的颜曾,如果你開發(fā)的類似信息流的 APP纠拔,應(yīng)該會加載大量的圖片,如果這時候按照默認(rèn)機制泛豪,緩存尺寸是沒有限制的稠诲,并且默認(rèn)的緩存周期是一周。 就很容易造成應(yīng)用存儲空間占用偏大的問題诡曙。

那么可能有人會說了臀叙,現(xiàn)在 iPhone 的存儲空間都很大,多緩存一點也不是問題吧? 但你是否知道 iOS 上還有一個用量查詢的功能呢价卤。在設(shè)置項中用戶可以查看每個 APP 的空間使用情況劝萤, 如果你的 APP 占用空間比較大的話,就很容易成為用戶的卸載目標(biāo)慎璧,這應(yīng)該是需要關(guān)注的一個細(xì)節(jié)床嫌。

另外跨释,過多的占用緩存空間其實并不一定有用。大部分情況是一些圖片被緩存下來后厌处,很少再被重復(fù)展現(xiàn)鳖谈。所以合理的規(guī)劃緩存空間尺寸還是很有必要的±妫可以這樣設(shè)置:

[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50M

maxCacheSize 是以字節(jié)來表示的缆娃,我們上面的計算代表 50M 的最大緩存空間。 把這行代碼寫在你的 APP 啟動的時候瑰排,這樣 SDWebImage 在清理緩存的時候贯要,就會清理多余的緩存文件了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椭住,一起剝皮案震驚了整個濱河市崇渗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌函荣,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扳肛,死亡現(xiàn)場離奇詭異傻挂,居然都是意外死亡,警方通過查閱死者的電腦和手機挖息,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門金拒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人套腹,你說我怎么就攤上這事绪抛。” “怎么了电禀?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵幢码,是天一觀的道長。 經(jīng)常有香客問我尖飞,道長症副,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任政基,我火速辦了婚禮贞铣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沮明。我一直安慰自己辕坝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布荐健。 她就那樣靜靜地躺著酱畅,像睡著了一般琳袄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上圣贸,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天挚歧,我揣著相機與錄音,去河邊找鬼吁峻。 笑死滑负,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的用含。 我是一名探鬼主播矮慕,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼啄骇!你這毒婦竟也來了痴鳄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤缸夹,失蹤者是張志新(化名)和其女友劉穎痪寻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虽惭,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡橡类,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了芽唇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顾画。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匆笤,靈堂內(nèi)的尸體忽然破棺而出研侣,到底是詐尸還是另有隱情,我是刑警寧澤炮捧,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布庶诡,位于F島的核電站,受9級特大地震影響咆课,放射性物質(zhì)發(fā)生泄漏灌砖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一傀蚌、第九天 我趴在偏房一處隱蔽的房頂上張望基显。 院中可真熱鬧,春花似錦善炫、人聲如沸撩幽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窜醉。三九已至宪萄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榨惰,已是汗流浹背拜英。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留琅催,地道東北人居凶。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像藤抡,于是被迫代替她去往敵國和親侠碧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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