1. 程式人生 > >SDWebImage,快取下載,快取管理,

SDWebImage,快取下載,快取管理,

要寫點關於SDWebImage的文章了,這段時間看的不少,總體的感受是SDWebImage的程式碼不如AFN那麼規整、有條理,並沒有吐槽的意思,稍微細細看一下就會有這樣的感受。本篇文章不會用大量的篇幅來介紹SDWebImage如何使用,而是更多地介紹SDWebImage的整體思路和一些實現細節,還有介紹一些不是特別常用的一些功能(因為有不少iOS開發人員還只是會使用sd_setImageWithURL)。首先我們要看一下SDWebImage的整體結構:
SDWebImage整體結構圖
這裡我要說明的一點是我當前使用SD的git提交版本是e41af47e2f5de9317d55083e23168e076b550e34(Sat Jan 30 02:54:23 2016 +0100)。讓我們看一下這張圖的內容。
可以將SDWebImage的框架分為三個部分:

1.適配
SDWebImage
iOS版本、編譯指令的適配、執行緒切換的巨集、還有一個匯出的行內函數,用於根據image的命名key 將image轉換成相應的scale的UIImage型別以完成對Image的縮放,如檔名帶@2x,將按照2倍縮放。

2.Util工具
核心的類就是SDWebImageManager它負責建立和管理下載任務、對快取操作進行管理,我們通常使用的UIImageView的WebCache分類下的sd_setImageWithURL方法的實現就依賴於這個類,其他View分類的設定圖片的方法也實現也類似。
SDWebImageManager實現下載依賴於下載器:SDWebImageDownloader,下載器負責管理下載任務,而執行下載任務是由SDWebImageDownloaderOperation操作完成。
SDWebImageManager實現快取依賴於快取管理:SDImageCache,能夠完成圖片的記憶體快取和磁碟快取,還可以查詢指定url的圖片是否進行了快取、取出快取等操作。
下載和快取的過程中會呼叫適配模組進行將圖片轉為合適的尺寸,使用解壓模組將被壓縮的圖片解壓後完成快取。

3.分類
包括兩部分:①.檢視分類、②.用於圖片格式處理和格式轉換的模組。

①.檢視分類
檢視分類中有一個基本的分類:
UIView+WebCacheOperation這個分類用於完成將組合操作(SD定義了能夠實現下載和快取的組合操作類SDWebImageCombinedOperation)與View繫結、取消繫結和移除繫結等功能。其他檢視分類的實現都依賴於這個分類。
MKAnnotationView+WebCache、UIImageView+WebCache、UIImageView+HighlightedWebCache對view中的圖片的載入過程的實現比較相似(後面會介紹),UIButton+WebCache分類中針對UIButton的不同的State可以設定不同的image。

②.用於圖片格式處理和格式轉換的模組
NSData+ImageContentType這個分類只有一個方法sd_contentTypeForImageData:,是根據圖片的二進位制data的第一個位元組的資料,得到圖片相應的MIME型別。
UIImage+MultiFormat也只有一個方法sd_imageWithData:,根據傳入的NSData,讀取到MIME型別然後轉換成對應的UIImage。
UIImage+GIF根據傳入的值如檔名或者NSData,得到對應的GIF圖的UIImage物件,實際上是一個animatedImage。
UIImage+WebP根據傳入的NSData,得到對應的WebP圖的UIImage物件,這個方法的實現依賴於WebP庫,需要到google下載libwebp。

以上是從程式碼的角度分析了SD可以完成的工作,而在github上SD的主頁可以看到,它的自我介紹中的主打功能:

提供UIImageView的一個分類,以支援網路圖片的載入與快取管理
一個非同步的圖片載入器
一個非同步的記憶體+磁碟圖片快取
支援GIF圖片
支援WebP圖片
後臺圖片解壓縮處理
確保同一個URL的圖片不被下載多次
確保虛假的URL不會被反覆載入
確保下載及快取時,主執行緒不被阻塞

本篇文章的內容主要涉及到4個類:SDWebImageDownloaderOptionsSDWebImageDownloaderSDImageCacheSDWebImageManager,詳細介紹如何實現下載和快取的以及如何在這個過程中做到上面提到的‘三個確保’。至於其他內容(如GIF和WebP圖片的載入)以後會一一介紹。

回到頂部

下載操作SDWebImageDownloaderOptions和下載過程實現

SDWebImage下載圖片使用的是SDWebImageDownloaderOperation,它是一個NSOperation的子類,同時遵守了<SDWebImageOperation>協議(其實這個協議只聲明瞭一個方法cancel用於取消操作)。這個操作負責管理下載的選項,進行網路訪問時的request,設定網路處理質詢的憑據,進行網路連線接收資料,管理網路訪問的response和是否解壓的選項等。總之,它的任務就是網路訪問配置、進行網路訪問以及處理資料。

每一個NSOperation都是為了完成一項任務而誕生的,而SDWebImageDownloaderOperation的任務就是負責依照指定的下載選項,使用將指定的urlRequest建立NSURLConnection物件進行網路連線(NSURLConnection物件的代理就是SDWebImageDownloaderOperation自己),進行對圖片的下載。在下載的過程中對圖片資料進行拼接,可以實現對進度progress的跟蹤,在下載之後可以將接收到的圖片資料轉換、解壓等,並完成一個下載完成的回撥。如果網路訪問過程中接收到質詢,則使用服務端憑據或者本地儲存的憑據處理質詢;如果下載失敗了,則傳送錯誤通知,執行完成回撥,並結束下載任務。

SDWebImageDownloaderOperation類主要有以下幾個屬性:
1.NSURLRequest *request:下載時進行網路請求的request,由構造方法傳入。
2.BOOL shouldDecompressImages:下載後是否需要解壓圖片。
3.BOOL shouldUseCredentialStorage:URLConnection是否需要諮詢憑據倉庫來對連線進行授權,預設是YES。
這是NSURLConnectionDelegate的-connectionShouldUseCredentialStorage:方法的返回值
4.NSURLCredential *credential:在-connection:didReceiveAuthenticationChallenge:方法中驗證質詢時使用的憑據
已經存在的request,URL的使用者名稱或密碼構成的憑據會覆蓋這個值,具體解釋參見SDWebImageDownloader部分。
5.SDWebImageDownloaderOptions options:readonly下載選項,由構造方法傳入。
6.NSInteger expectedSize:預期的檔案長度,使用NSInteger完全夠用。
7.NSURLResponse *response:connection物件進行網路訪問,接收到的的response
要注意的是:下載選項是在SDWebImageDownloader中定義的,SDWebImageDownloader是下載器負責管理下載佇列和控制下載過程(通過呼叫SDWebImageDownloaderOperation的方法)。下載選項SDWebImageDownloaderOptions的定義如下:

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    /// 漸進式下載,如果設定了這個選項,會在下載過程中,每次接收到一段chunk資料就呼叫一次完成回撥(注意是完成回撥)回撥中的image引數為未下載完成的部分影象
    SDWebImageDownloaderProgressiveDownload = 1 << 1,

    /// 通常情況下request阻止使用NSURLCache. 這個選項會用預設策略使用NSURLCache 
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    /// 如果從NSURLCache中讀取圖片,會在呼叫完成block時,傳遞空的image或imageData \
     * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,

    /// 系統為iOS 4+時,如果應用進入後臺,繼續下載。這個選項是為了實現在後臺申請額外的時間來完成請求。如果後臺任務到期,操作會被取消。
    SDWebImageDownloaderContinueInBackground = 1 << 4,

    /// 通過設定NSMutableURLRequest.HTTPShouldHandleCookies = YES的方式來處理儲存在NSHTTPCookieStore的cookies
    SDWebImageDownloaderHandleCookies = 1 << 5,

    /// 允許不受信任的SSL證書,在測試環境中很有用,在生產環境中要謹慎使用
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    /// 將圖片下載放到高優先順序佇列中
    SDWebImageDownloaderHighPriority = 1 << 7,
};

這些選項主要涉及到下載的優先順序、快取、後臺任務執行、cookie處理以及證書認證幾個方面,在建立下載操作的時候可以使用組合的選項以完成一些特殊的需求。

SDWebImageDownloaderOperation只對外提供了一個物件方法- initWithRequest: options: progress: completed: cancelled:,它使用預設的屬性值初始化一個SDWebImageDownloaderOperation物件。

下面我們看一下SDWebImageDownloaderOperation對NSOperation的-start方法的重寫,畢竟這是完成下載任務的核心程式碼。以下是將-start提取出來的部分程式碼

@synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset]; // 將各個屬性置空。包括取消回撥、完成回撥、進度回撥,用於網路連線的connection,用於拼接資料的imageData、記錄當前執行緒的屬性thread。
            return;
        }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
 // 使用UIApplication的beginBackgroundTaskWithExpirationHandler方法向系統借用一點時間,繼續執行下面的程式碼來完成connection的建立和進行下載任務。
 // 在後臺任務執行時間超過最大時間時,也就是後臺任務過期執行過期回撥。在回撥主動將這個後臺任務結束。
 /*        ^{                __strong __typeof (wself) sself = wself;                if (sself) {                    [sself cancel];                    [app endBackgroundTask:sself.backgroundTaskId];                    sself.backgroundTaskId = UIBackgroundTaskInvalid;                }            } */
#endif
        self.executing = YES; // 標記狀態
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; // 建立用於下載的connection
        self.thread = [NSThread currentThread]; // 記錄當前執行緒
    }

    [self.connection start]; 

    if (self.connection) {
        if (self.progressBlock) { // 任務開始立刻執行一次進度回撥
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{ // 傳送開始下載的通知,object為operation本身
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
        
        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            CFRunLoopRun();
        }
        // 當runloop開啟之後,執行緒切換到runloop中的任務,開始下載圖片,所以下面的程式碼是經過一段時間的延遲執行的,也就是當connection的網路訪問進行之後,才會執行下面的程式碼。
        // 這個時候可以進行一些判斷,如圖片是否被正確地下載完成。
        if (!self.isFinished) {
            [self.connection cancel];

            // NSURLConnectionDelegate代理方法
            // 主動呼叫 並製造一個錯誤,這樣做的目的是因為這個方法一旦呼叫,代理就不會再接收connection的訊息,也就是不在呼叫其他的任何代理方法了,connection徹底結束。
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    } else { // connectin 建立失敗,這裡直接執行完成回撥,並傳遞一個connection沒有初始化的錯誤
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

    // 執行到這裡說明下載操作已經完成(無論成功還是失敗),因此沒有必要在後臺執行。使用endBackgroundTask:
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif

這些就是一次下載操作要執行的任務,但是資料處理是下載任務的關鍵,SDWebImageDownloaderOperation通過NSURLConnection的代理方法完成對下載的圖片的資料處理,主要用到以下幾個方法:

// NSURLConnectionDataDelegate中宣告
connection: didReceiveResponse: // 接收到服務端的response時,執行一次
connection: didReceiveData: // 每次接收到chunk資料都會呼叫
connectionDidFinishLoading: // 當連線結束的時候呼叫一次
connection: willCacheResponse: // 要進行快取之前呼叫

// NSURLConnectionDelegate中宣告 
connection: didFailWithError: // 連線失敗,或者沒有成功下載完成呼叫
connectionShouldUseCredentialStorage: // 指定是否需要使用本地憑據進行驗證
connection: willSendRequestForAuthenticationChallenge: // 處理服務端過來的質詢

下面我們看一下除了上面標註的一些基本的功能外,SDWebImageDownloaderOperation在每個方法內部還有哪些細節性的工作。
1.connection: didReceiveResponse:
主要完成以下工作:

if (statusCode<400並且不等於304) {
    // 設定預期檔案長度屬性的值
    // 立刻完成一次進度回撥,傳遞的引數為0,
    // 初始化用於拼接圖片二進位制資料的屬性imageData
    // 設定response屬性為服務端返回的response值
    // 向主佇列同步傳送一個接收到response的通知
} else {
    // 如果statusCode為304,也就是服務端Not Modified並且拿到了本地的HTTP快取,取消操作,傳送操作停止的通知,執行完成回撥,停止當前的runloop,設定下載完成標記為YES,正在執行標記為NO,將屬性置空。
}

2.connection: didReceiveData:

使用自身屬性imageData拼接接收的資料
if (下載選項設定了SDWebImageDownloaderProgressiveDownload) {
    取得已經拼接完的imageData,建立一個CGImageSourceRef型別的imageSouce,使用imageSouce建立CGImageRef型別的物件partialImageRef,代表著要下載的圖片的一部分,調整方向並將使用`UIImage imageWithCGImage:partialImageRef`將其匯出為UIImage,釋放掉partialImageRef,並在主執行緒同步執行一次完成回撥,指定第一個引數為剛才到處的UIImage,最後釋放掉imageSource佔用的空間。
}
執行一次進度回撥progressBlock,第一個引數傳遞已經拼接的imageData的長度

3.connectionDidFinishLoading:
執行完這個方法之後,代理不會再接收任何connection傳送的訊息,意味著下載完成。通常情況下,下載任務正常結束之後,就會執行一次這個方法。

@synchronized(self) {
    停止當前的RunLoop,將connection屬性和thread屬性置空,傳送下載停止的通知。
}
檢查sharedURLCache是否快取了這次下載response,如果沒有就將responseFromCached設定為NO
執行完成回撥completionBlock,並根據是否讀取了快取、圖片尺寸是否為(0,0)等條件向完成回撥傳遞不同的值。
將完成狀態、執行狀態的標記復位、將屬性置空

4.connection: didFailWithError:
執行完這個方法之後,代理不會再接收任何connection傳送的訊息,意味著下載失敗。通常情況下,下載任務非正常結束,就會執行一次這個方法。

@synchronized(self) {
        停止當前的RunLoop,將connection屬性和thread屬性置空,傳送下載停止的通知。
}

if (self.completedBlock) { // 只使用這一種引數傳遞的方式完成回撥
    self.completedBlock(nil, nil, error, YES);
}
將完成狀態、執行狀態的標記復位、將屬性置空

5.connection: willCacheResponse:
快取response之前呼叫一次這個方法,給connection的代理一次機會改變它。可以返回一個修改之後的response,或者返回nil不儲存快取。
SDWebImageDownloaderOperation在這個方法內部完成了以下工作:

responseFromCached = NO; // 標記這次下載的圖片不是從快取中讀取出來的
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) { 
    return nil;  // 如果request的快取策略(實際上Downloader在使用操作進行下載的時候,會根據下載選項修改request的快取策略)是忽略本地快取,不進行不進行快取
} else {
    
            
           

相關推薦

Linux學習匯總——Linux用戶組管理文件權限管理文本處理工具grep及egrep

linux用戶組管理 linu文件權限管理 linu文本管理 egrep grep 本章Blog相關Linux相關知識點解析:在數據庫按搜索碼查找相對應的條目,並找與之對應額外的其他數據庫的過程;名稱解析:UID ,組名解析:GID數據庫:文本文件,sql數據庫,ldap數據庫,用戶庫:/

LINUX——gitlab版本控制關於項目組管理項目用戶及權限管理的使用

entos 開發 chan 網頁 1.2 保存 ise roo 能力 gitlab一般用於:1.開發工程中各種文件變更的管理最主要的功能是追蹤文件的變更,將信息記錄下來。每一次文件的改變,版本號都會增加 2.並行開發軟件開發時往往是多人協同,而版本控制可以解決版本同步以及不

微信小程式業務域名配置:校驗檔案驗證失敗下載校驗檔案上傳到伺服器指定的目錄

1.校驗檔案內容錯誤。校驗檔案內容一般是非HTML資料,如果下載下來的校驗檔案內容為HTML資料,一般為登入態過期。請重新登入小程式下載校驗檔案。 2.https證書過期。請確保https證書處於有效期內。 3.使用curl 測試連結,確保curl能夠正常訪問連結。

SDWebImage快取下載快取管理

要寫點關於SDWebImage的文章了,這段時間看的不少,總體的感受是SDWebImage的程式碼不如AFN那麼規整、有條理,並沒有吐槽的意思,稍微細細看一下就會有這樣的感受。本篇文章不會用大量的篇幅來介紹SDWebImage如何使用,而是更多地介紹SDWebIm

JDK1.8 IntegerLong等的快取策略

1 public class IntegerTest { 2 public static void main(String[] args) { 3 Integer a = 10; 4 Integer b = 10; 5 System.ou

springboot 配置ehcache快取通過註解定製多租戶(multiTenantId)生成快取的key並且實現註解按照tenantId清除快取,tanant之間快取互相不影響

調研背景     本公司有一項功能需求,由於查詢的資料太多會導致訪問時間超優化API介面,但是這不是長久之計,便決定引入快取,但是此 快取能夠實現按照不同租戶的ID號碼在同一個cacheName中去生成能識別租戶的key,而且在使用cacheEvict時候

vue專案中如何對static資料夾下的靜態檔案新增時間戳以達到清除快取

例如config.js檔案是存放在static資料夾下,裡面存放的是websocket資訊,需要經常改動。改動了以後由於快取資訊,使其不生效,因此需要對引入的檔案新增時間戳。 方法如截圖所示: <script id="main"></script><script type="

安卓專案實戰之強大的網路請求框架okGo使用詳解(六):擴充套件專案okServer更強大的下載上傳功能支援斷點和多工管理

OkGo與OkDownload的區別就是,OkGo只是簡單的做一個下載功能,不具備斷點下載,暫停等操作,但是這在很多時候已經能滿足需要了。 而有些app需要有一個下載列表的功能,就像迅雷下載一樣,每個下載任務可以暫停,可以繼續,可以重新下載,可以有下載優先順序,這時候OkDownload就有

idea的svn下載下來的程式碼無法更新且沒有被svn管理No versioned directories to update was found解決方案

問題:idea通過VCS/Checkout from Version Controller下載下來的程式碼,發現更新程式碼後無法提交,也無法更新。 解決辦法: 1.首先進入idea的File/Settings/Version Controller/Subversion配置 2.上一步完成後

springboot 2.x 快取功能基於redis封裝快取

spring boot整合redis進行資料快取功能         @Cacheable 表明Spring在呼叫方法之前,首先應該在快取中查詢方法的返回值。如果這個值能夠找到,就會返回快取的值。否則的話,這個方法就

11.17 域名解析DNS---------快取記憶體DNS權威DNS的正反向解析輪詢:域名轉換,郵箱解析內部解析和外部解析

1.概念的介紹 1)DNS DNS(Domain Name System,域名系統),全球資訊網上作為域名和IP地址相互對映的一個分散式資料庫,能夠使使用者更方便的訪問網際網路,而不用去記住能夠被機器直接讀取的IP數串。 2)快取記憶體DNS服務 快取記憶體DNS服務的作用:正常上網

真男人敢嚐鮮:使用Chrome擴充套件iSearch美化醜陋標籤頁改造難用的書籤定期清理快取提升福利學術程式碼等搜尋效率

先分享一個牛X的Chrome外掛——iSearch,有時間再寫一篇文章,介紹另外一個實用的小工具。 摘要: 本文介紹,如何使用擴充套件iSearch,打造高隱私,高顏值,高效率的Chrome。 iSearch功能太多,按照使用場景,列舉本人用到的功能。 外掛iSearch的使用場景:

J2Cache 和普通快取框架有何不同它解決了什麼問題?

不少人看到 J2Cache 第一眼時,會認為這就是一個普普通通的快取框架,和例如 Ehcache、Caffeine 、Spring Cache 之類的專案沒什麼區別,無非是造了一個新的輪子而已。事實上完全不是一回事! 目前快取的解決方案一般有兩種: 記憶體快取(

java關於使用subList方法擷取的字串放入redis快取的相關問題及解決方法

    在前幾天做專案的時候,會對其他專案通過阿里雲發來的訊息中的某個List型別的欄位進行擷取,並將擷取後的結果存入redis中。但是在專案執行起來的時候,獲取redis中該欄位的內容會出現錯誤,錯誤提示如下: (error) WRONGTYPE Operation a

快取穿透快取失效(快取雪崩)和快取併發

快取穿透: 通常快取都是根據key去查詢value,如果快取中不存在,則去DB中查詢,如果查詢到了則將此key->value寫入快取。但是,對於某些一直不存在的資料,每次都無法在快取中查詢到,所

系統架構解析-讀寫分離水平切分及快取架構對比

  最近在研究一些系統架構方案,學習到讀寫分離的時候,對於讀寫分離應用場景有了一些自己的理解: 一. 讀寫分離 1. 什麼是資料庫讀寫分離   首先我們看一個讀寫分離架構圖:   讀寫分離就是:一主多從,讀寫分離,主動同步,是一種常見的資料庫架構,一般來說: 主庫:提供

http快取快取和協商快取

原文連結:http://caibaojian.com/http-cache-3.html 下面我貼出2道題,大家可以嘗試解答下:· 以下為 page.html 內容: <!DOCTYPE html><html xmlns="http://www.w3.org/1999/

redis資料一致性開發中關於快取和資料同步問題

在開發中出現很多關於快取和資料共存問題,本小G網上翻閱cache aside pattern 一些資料,加上專案體驗寫下 寫下這一小簡,大家一塊來探討: 使用場景:在使用redis來做資料快取,減輕資料壓力和速度,但是有一個問題就是快取和my

【116】vue-router使用懶載入機制在生產環境中如何避免瀏覽器快取Webpack 3 編譯後生成的js路徑導致404錯誤。(二)

整理思路 要解決這個問題,F5 重新整理是最好的解決辦法。但是每次釋出新版本後,都要求使用者主動按 F5 重新整理瀏覽器,會讓使用者覺得不方便。這對於快速迭代的產品來說尤其突出。 所以為了方便使用者使用,我們希望當前端頁面修改之後,系統能夠自動重新整理頁

【115】vue-router使用懶載入機制在生產環境中如何避免瀏覽器快取Webpack 3 編譯後生成的js路徑導致404錯誤。(一)

前言 為了適應不斷變化的市場需求,軟體產品需要持續部署。生產環境的部署週期往往短則一週,長則半個月。在這一持續部署的過程中,前端開發人員要面臨一個問題:生產環境部署了新版本的程式碼後,如果使用者沒有 F5 重新整理瀏覽器,就會導致瀏覽器快取Webpack 3