1. 程式人生 > >天天都在用的 SDWebImage, 你瞭解它的快取策略嗎?

天天都在用的 SDWebImage, 你瞭解它的快取策略嗎?

2017年02月19日 - 作者: SwiftCafe

SDWebImage 相信對大多數開發者來說,都是一個不陌生的名字。它除了幫助我們讀取網路圖片,還會處理這些圖片的快取。它的快取機制到底是什麼樣的呢,讓我給跟大家嘮叨嘮叨,希望你能有收穫。

基本結構

閒言少敘,咱們這就開始。 首先咱們來看看 SDWebImage 的整體結構:

1

有一個專門的 Cache 分類用來處理圖片的快取。 這裡面也有兩個類 SDImageCache 和 SDImageCacheConfig。 大部分的快取處理都在 SDImageCache 這個類中實現。其他幾個資料夾咱們分別有個字的功能,因為咱們這次專門討論快取策略,所以其他內容暫時略過。

Memory 和 Disk 雙快取

首先,SDWebImage 的圖片快取採用的是 Memory 和 Disk 雙重 Cache 機制, 聽起來挺高大上吧。其實也不復雜。

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

@interface SDImageCache ()
#pragma mark - Properties
@property (strong, nonatomic, nonnull) NSCache *memCache;
...

這裡我們發現, 有一個叫做 memCache 的屬性,它是一個 NSCache 物件,用於實現我們對圖片的 Memory Cache。 SDWebImage 還專門實現了一個叫做 AutoPurgeCache 的類, 相比於普通的 NSCache, 它提供了一個在記憶體緊張時候釋放快取的能力:

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

其實就是接受系統的記憶體警告通知,然後清除掉自身的圖片快取。 這裡大家比較少見的一個類應該是 NSCache 了。 簡單來說,它是一個類似於 NSDictionary 的集合類,用於在記憶體中儲存我們要快取的資料。詳細資訊大家可以參考官方文件: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 檔名, 存放到檔案中。

整體機制

為了節約篇幅,提升大家的閱讀體驗,這裡儘量少貼大段程式碼。 我們前面介紹了 SDWebImage 同時使用記憶體和硬碟兩種快取。 那麼我們來看看當使用 SDWebImage 讀取圖片時候的完整流程。 我們一般會使用 SDWebImage 對 UIKit 的擴充套件,直接載入圖片:

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

首先這個 Category 方法 sd_setImageWithURL 內部會呼叫 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 內部的 downloadImageWithURL 方法會先使用我們前面提到的 SDImageCache 類的 queryDiskCacheForKey 方法,查詢圖片快取:

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

再來看 queryDiskCacheForKey 方法內部, 先會查詢 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 查詢成功,還會把得到的圖片再次設定到 Memory Cache 中。 這樣做可以最大化那些高頻率展現圖片的效率。

如果快取查詢成功, 那麼就會直接返回快取資料。 如果不成功,接下來就開始請求網路:

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

請求網路使用的是 imageDownloader 屬性,這個示例專門負責下載圖片資料。 如果下載失敗, 會把失敗的圖片地址寫入 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 預設會有一個對上次載入失敗的圖片拒絕再次載入的機制。 也就是說,一張圖片在本次會話載入失敗了,如果再次載入就會直接拒絕。SDWebImage 這樣做可能是為了提高效能吧。這個機制可能會容易被大家忽略,所以這裡特意提一下,說不定哪天遇到一些奇怪問題時候,這個知識點會幫你快速定位問題~

如果下載圖片成功了,接下來就會使用 [self.imageCache storeImage] 方法將它寫入快取,並且呼叫 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 的整體圖片載入流程就都走完了。 由於要控制篇幅,我這裡只挑了最重點的幾個節點寫出來,SDWebImage 的完整機制肯定會更全面,先幫大家疏通思路。

是否要重試失敗的 URL

SDWebImage 的整體圖片處理流程咱們體驗了一遍。 那麼有哪些重要的點對咱們使用它會有幫助呢? 我幫大家整理了一些。

你可以在載入圖片的時候設定 SDWebImageRetryFailed 標記,這樣 SDWebImage 就會載入之前失敗過的圖片了。 記得我們前面提到的 failedURLs 屬性了吧,這個屬性是在記憶體中儲存的,如果圖片載入失敗, SDWebImage 會在本次 APP 會話中都不再重試這張圖片了。當然這個載入失敗是有條件的,如果是超時失敗,不記在內。

總之,如果你更需要圖片的可用性,而不是這一點點的效能優化,那麼你就可以帶上 SDWebImageRetryFailed 標記:

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

Disk 快取清理策略

SDWebImage 會在每次 APP 結束的時候執行清理任務。 清理快取的規則分兩步進行。 第一步先清除掉過期的快取檔案。 如果清除掉過期的快取之後,空間還不夠。 那麼就繼續按檔案時間從早到晚排序,先清除最早的快取檔案,直到剩餘空間達到要求。

具體點,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];

分別在應用進入後臺和結束的時候,遍歷所有的快取檔案,如果快取檔案超過 maxCacheAge 中指定的時長,就會被刪除掉。

同樣的, maxCacheSize 控制 SDImageCache 所允許的最大快取空間。 如果清理完過期檔案後快取空間依然沒達到 maxCacheSize 的要求, 那麼就會繼續清理舊檔案,直到快取空間達到要求為止。

瞭解了這個機制對我們有什麼幫助呢? 我們來繼續講解,我們平時在使用 SDWebImage 的時候是沒接觸過它們的。那麼以此推理,它們一定有預設值,也確實有:

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

上面是 maxCacheAge 的預設值,註釋上寫的很清楚,快取一週。 再來看看 maxCacheSize。 翻了一遍 SDWebImage 的程式碼,並沒有對 maxCacheSize 設定預設值。 這就意味著 SDWebImage 在預設情況下不會對快取空間設限制。

這一點可以在 SDWebImage 清理快取的程式碼中求證:

if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
    //清理快取程式碼
}

說明一下, 上面程式碼中的 currentCacheSize 變數代表當前圖片快取佔用的空間。 從這裡可以看出, 只有在 maxCacheSize 大於 0 並且當前快取空間大於 maxCacheSize 的時候才進行第二步的快取清理。

這意味著什麼呢? 其實就是 SDWebImage 在預設情況下是不對我們的快取大小設限制的,理論上,APP 中的圖片快取可以佔滿整個裝置。

SDWebImage 的這個特性還是比較容易被大家忽略的,如果你開發的類似資訊流的 APP,應該會載入大量的圖片,如果這時候按照預設機制,快取尺寸是沒有限制的,並且預設的快取週期是一週。 就很容易造成應用儲存空間佔用偏大的問題。

那麼可能有人會說了,現在 iPhone 的儲存空間都很大,多快取一點也不是問題吧? 但你是否知道 iOS 上還有一個用量查詢的功能呢。在設定項中使用者可以檢視每個 APP 的空間使用情況, 如果你的 APP 佔用空間比較大的話,就很容易成為使用者的解除安裝目標,這應該是需要關注的一個細節。

另外,過多的佔用快取空間其實並不一定有用。大部分情況是一些圖片被快取下來後,很少再被重複展現。所以合理的規劃快取空間尺寸還是很有必要的。可以這樣設定:

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

maxCacheSize 是以位元組來表示的,我們上面的計算代表 50M 的最大快取空間。 把這行程式碼寫在你的 APP 啟動的時候,這樣 SDWebImage 在清理快取的時候,就會清理多餘的快取檔案了。

結語

這次跟大家聊了聊 SDWebImage 整體流程,以及它的快取策略。SDWebImage 是一個歷時很久的開源庫,並且它不斷的保持著更新。 雖然是一個並不算很複雜的開源庫,但仔細研讀一下它的程式碼, 會發現它的內部很多機制設計的還是很巧妙的。 為了保證大家的閱讀體驗,儘量控制文章的篇幅,這裡儘量選出對大家最有幫助的內容和大家分享。這篇文章結構整理花了幾天時間,仔細篩選最重要的內容。想達到的效果就是,讓他讀起來不累,但卻能很快抓住重點,讓大家得到有用的資訊。也希望大家對閱讀體驗能夠提出反饋,幫助我給大家創造更好的內容。

如果想了解 SDWebImage 更詳細的內容,那麼它的 Github 主頁就是最好的地方了,在這裡也貼出來,供大家參考: https://github.com/rs/SDWebImage

如果你覺得這篇文章有幫助,還可以關注微信公眾號 swift-cafe,會有更多我的原創內容分享給你~

本站文章均為原創內容,如需轉載請註明出處,謝謝。

相關推薦

天天SDWebImage 瞭解快取策略

2017年02月19日 - 作者: SwiftCafe SDWebImage 相信對大多數開發者來說,都是一個不陌生的名字。它除了幫助我們讀取網路圖片,還會處理這些圖片的快取。它的快取機制到底是什麼樣的呢,讓我給跟大家嘮叨嘮叨,希望你能有收穫。 基本結構

Java 泛型瞭解型別擦除

大家可能會有疑問,我為什麼叫做泛型是一個守門者。這其實是我個人的看法而已,我的意思是說泛型沒有其看起來那麼深不可測,它並不神祕與神奇。泛型是 Java 中一個很小巧的概念,但同時也是一個很容易讓人迷惑的知識點,它讓人迷惑的地方在於它的許多表現有點違反直覺。

轉:Java 泛型瞭解型別擦除

泛型,一個孤獨的守門者。 大家可能會有疑問,我為什麼叫做泛型是一個守門者。這其實是我個人的看法而已,我的意思是說泛型沒有其看起來那麼深不可測,它並不神祕與神奇。泛型是 Java 中一個很小巧的概念,但同時也是一個很容易讓人迷惑的知識點,它讓人迷惑的地方

習慣腳手架的, 瞭解Webpack這些知識點?

大概準備春招兩個月了, 也沒找到坑位埋自己, 來看看webpack webpack 官網: www.webpackjs.com 對於前端的大兄弟來說, 每天在前端摸爬滾打, 各方征戰, 那對於webpack肯定再熟悉不過了 所以說這篇文章適合給像我們這樣的後端的同胞看著玩,(一說前端不是想用BootStr

Linux裡隱藏的計算器知道的奧祕

## Linux裡隱藏的計算器,你知道它的奧祕嗎? 大家都知道,windows下有個計算器工具,我們在工作生活中經常使用到它。但是,你可知Linux下也同樣有個計算器嗎? 當然,良許說的是命令列下的計算器工具,而不是介面型的計算器。良許是Linux應用開發工程師,平時基本是在命令列下工作,所以對於介面類的

了這麼多年的泛型到底有多瞭解

現代程式設計師寫程式碼沒有人敢說自己沒用過泛型,這個泛型模板T可以被任何你想要的型別替代,確實很魔法很神奇,很多人也習以為常了,但就是這麼有趣的泛型T底層到底是怎麼幫你實現的,不知道有多少人清楚底層玩法,這篇我就試著來分享一下,不一定全對哈。。。 一:沒有泛型前 現在的netcore 3.1和最新的.netf

了這麼多年的 Java 泛型到底有多瞭解

> 本篇文章 idea 來自[用了這麼多年的泛型,你對它到底有多瞭解?](https://www.cnblogs.com/huangxincheng/p/12764925.html),恰好當時看了「深入 Java 虛擬機器的第三版」瞭解泛型的一些歷史,感覺挺有意思的,就寫了寫 Java 版的泛型。 作為一個

每天String真的瞭解

# 1.String概述 > `java.lang.String` 類代表字串。Java程式中所有的字串文字(例如"abc")都可以被看作是實現此類的例項 > > String 中包括用於檢查各個字串的方法,比如用於比較字串,搜尋字串,提取子字串以及建立具有翻譯為大寫或小寫的所有字元的字串的副本。 # 2

IG奪冠!王思聰這麼努力還不瞭解下QbaoNetwork

前幾天,小Q 的朋友圈充斥著狂歡節一般的氣氛,被IG 刷屏了。 一半的人在為IG 加油、歡呼,“IG 衝鴨”、“IG 冠軍”、“IG 牛批”…… 另一半人在問IG 是誰~ 據小Q 瞭解,IG 是由“國民老公王思聰”一手打造的電競戰隊,成立於2011年。在2018

天天作業系統真的造

目錄 一、什麼是作業系統 二、作業系統的發展 三、作業系統的組成 四、作業系統如何啟動 五、程式在作業系統上執行 一、什麼是作業系統 一臺計算機包含硬體及軟體,作業系統是控制一臺計算機所有操作的軟體。它提供了使用者可以儲存和檢索檔案的工具,提供了使用者可以請求程式執行的介面,還提供了程式執行

ClassLoader 是 Java 屆最神祕的技術之一瞭解有幾分?

一、什麼是ClassLoader 顧名思義,它是用來載入 Class 的。它負責將 Class 的位元組碼形式轉換成記憶體形式的 Class 物件。位元組碼可以來自於磁碟檔案 *.class,也可以是 jar 包裡的 *.class,也可以來自遠端伺服器提供的位元組流,位元組碼的本質就是一個位元

天天訊息佇列卻不知道為啥要MQ這就有點尷尬了

1、為什麼要使用訊息佇列? 分析:一個用訊息佇列的人,不知道為啥用,有點尷尬。沒有複習這點,很容易被問蒙,然後就開始胡扯了。 回答:這個問題,咱只答三個最主要的應用場景(不可否認還有其他的,但是隻答三個主要的),即以下六個字:解耦、非同步、削峰 (1)解耦 傳統模式:   傳統模式的缺點:

天天Redis持久化方案又知道哪些?

前言 文章首發於微信公眾號【碼猿技術專欄】:天天用Redis,持久化方案有哪些你知道嗎? Redis目前已經成為主流的記憶體資料庫了,但是大部分人僅僅是停留在會用的階段,你真的瞭解Redis內部的工作原理嗎? 今天這篇文章將為大家介紹Redis持久化的兩種方案,文章將會從以下五個方面介紹: 什麼是RDB,R

每天在支付真的瞭解資訊流和資金流?

> 作為一個財務類的產品經理,除了每天被財務“虐待”千百遍,還需要對整個資金流向很清楚:錢給誰,怎麼給,怎麼做逆向流程,誰參與容錯等。財務很在意資金的流轉安全,但又極不願意花時間關注它。諸如此類:“我只是想要加個款”, “什麼,錢流轉出了問題?”,”還要我去操心?“ ![tapd_30172956_base

8個節點每個節點上布置6個ROS組裝250讀出251,事例率為645.3Hz

所在 ros mage images alt 黃色 節點 網段 cnblogs 組裝都用250網段,讀出都用251網段。除了黃色部分以外的節點都是cmm03節點。 平均事例率為:645.26Hz, ros所在節點的cpu idle 為17%。 8個節點,每個節點上

寫給立誌做碼農的大學生(蘑菇街掛了還要面騰訊? 我去我一定要去)

鍵盤 前言 docker 二本 征求意見 小時 形勢 我沒 妹子 先簡單介紹一下我自己,我是一所普通大學的本科生,大學錄取時的專業是非計算機系的,在大一下學期意識到自己喜歡敲代碼以後,就提交了轉專業申請。大二起開始在計算機系學習。大三時(2015年4月)拿到了騰訊暑期實習的

線上抓娃娃機日進鬥金確定真的是個風口

抓娃娃去年開始,在資本市場的推動下,新一輪的線下娃娃機熱潮興起,而近期,線上抓娃娃App也走紅起來,在App Store輸入“抓娃娃”關鍵字,一下子就能跳出數十個搜索結果。據《IT時報》記者采訪獲悉,經營情況好的時候,線上一臺娃娃機一天可能就有1000元以上的收入線上娃娃機,將會是怎麽樣一個風口呢?IT時報記

Linux裏隱藏的計算器知道的奧秘

流行 就是 工具 樹莓派 運算 返回 一個 語句 並且 大家都知道,windows下有個計算器工具,我們在工作生活中經常使用到它。但是,你可知Linux下也同樣有個計算器嗎? 當然,良許說的是命令行下的計算器工具,而不是界面型的計算器。良許是Linux應用開發工程師,平時基

遊戲音樂製作也有經典法則瞭解多少

遊戲依靠畫面和劇情吸引玩家,背景音樂感染玩家,給玩家最真實的遊戲體驗,為了能使遊戲背景音樂有更好的表現,在實際的遊戲音樂製作過程中,也有自己的經典法則,今天跟著奇億音樂小編一起來看看這些法則是什麼。         業內人士都知道,音訊源

C++有物件有麼?

聽過一個笑話, 程式設計師們互相聊天, 程式設計師A問:“為什麼C++比C麻煩那麼多?” 程式設計師B回答:“有了物件能不麻煩麼。” 在學習C/C++或者想要學習C/C++可以加入我們的學習交流QQ群:835257103,群內有學習資源,大家一起學習交流  情人節