2018.9.24html
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; 複製代碼
在 block 中獲得圖片下載進度和圖片加載完成(下載完成或者讀取緩存)的回調,若是你在圖片加載完成前取消了請求操做,就不會收到成功或失敗的回調ios
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { ... completion code here ... }]; 複製代碼
單獨使用 SDWebImageManagergit
SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager loadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; 複製代碼
單獨使用 SDWebImageDownloader 異步下載圖片github
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; [downloader downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { if (image && finished) { // do something with image } }]; 複製代碼
單獨使用 SDImageCache 異步緩存圖片 SDImageCache
支持內存緩存和異步的磁盤緩存(可選),可使用單例,也能夠建立一個有獨立命名空間的 SDImageCache
實例。 添加緩存的方法:segmentfault
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
複製代碼
默認狀況下,圖片數據會同時緩存到內存和磁盤中,若是你想只要內存緩存的話,可使用下面的方法:數組
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey toDisk:NO]; 複製代碼
讀取緩存時可使用 queryDiskCacheForKey:done:
方法,圖片緩存的 key 是惟一的,一般就是圖片的 absolute URL。緩存
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"]; [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) { // image is not nil if image was found }]; 複製代碼
MKAnnotationView :地圖大頭針 屬於 MapKit 框架的一個類,繼承自 UIView,是用來展現地圖上的 annotation 信息的,它有一個用來設置圖片的屬性 image。 官方文檔 MKMapView 的使用安全
流程圖圖源:J_Knight_:SDWebImage源碼解析,講解清晰,十分感謝。bash
在一個UIImageView調用: sd_setImageWithURL: placeholderImage: options: progress: completed:
markdown
nil
,就經過 Manager
單例開啓圖片加載的operation在downloadImageWithURL:options:progress:completed:
中會先拿圖片緩存的 key (默認是圖片 URL)去 Cache
單例中讀取內存緩存,若是有,就返回給 Manager
; 若是沒有,就開啓異步線程,拿通過 MD5 處理的 key 去讀取磁盤緩存,若是找到磁盤緩存了,就同步到內存緩存,而後再返回給 Manager
。 downloadImageWithURL
會返回一個 SDWebImageCombinedOperation
對象,這個對象包含一個 cacheOperation 和一個 cancelBlock。
若是內存和磁盤緩存中都沒有圖片,Manager
就會調用 Downloader
單例的 -downloadImageWithURL: options: progress: completed:
方法去下載,先將傳入的 progressBlock
和 completedBlock
保存起來,並在第一次下載該 URL 的圖片時,建立一個 NSMutableURLRequest
對象和一個 SDWebImageDownloaderOperation
對象,並將該 SDWebImageDownloaderOperation
對象添加到downloadQueue
來啓動異步下載任務。
DownloaderOperation
中包裝了一個 NSURLConnection
的網絡請求,並經過 runloop 來保持 NSURLConnection
在 start 後、收到響應前不被幹掉,下載圖片時,監聽 NSURLConnection
回調的 -connection:didReceiveData:
方法中會負責 progress 相關的處理和回調,- connectionDidFinishLoading:
方法中會負責將 data 轉爲 image,以及圖片解碼操做,並最終回調 completedBlock。
DownloaderOperation
中的圖片下載請求完成後,會回調給 Downloader
,而後 Downloader
再回調給 Manager
,Manager
將圖片緩存到內存和磁盤上(可選),並回調給 UIImageView
,UIImageView
中再回到主線程設置 image
屬性。
SDImageCache
管理着一個內存緩存和磁盤緩存(可選),同時在寫入磁盤緩存時採起異步執行,不會阻塞主線程。
爲何須要緩存?
內存緩存經過一個繼承 NSCache
的 AutoPurgeCache
類實現。
NSCache (NSCache官方文檔) NSCache
是一個相似於 NSMutableDictionary
存儲 key-value 的容器,特色以下:
NSCache
會自動刪除一些緩存對象NSCache
對象進行增刪改查時,不需加鎖NSMutableDictionary
,NSCache
存儲對象時不會對 key 進行 copy 操做磁盤緩存經過異步操做 NSFileManager
存儲緩存文件到沙盒實現。
-init
方法中默認調用了 -initWithNamespace:
方法,-initWithNamespace:
方法又調用了 -makeDiskCachePath:
方法來初始化緩存目錄路徑, 同時還調用了 -initWithNamespace:diskCacheDirectory:
方法來實現初始化。 初始化方法調用棧:-init
-initWithNamespace:
-makeDiskCachePath:
-initWithNamespace:diskCacheDirectory:
複製代碼
-initWithNamespace:diskCacheDirectory:
初始化實例變量、屬性,設置屬性默認值,並根據 namespace 設置完整的緩存目錄路徑,除此以外還添加了通知觀察者,用於內存緊張時清空內存緩存,以及程序終止運行時和程序退到後臺時清掃磁盤緩存。
storeImage:
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk{ } 複製代碼
寫入的參數有三個。 添加內存緩存時,先計算像素,再加進去 添加磁盤緩存時,若是須要在存儲以前將傳進來的 image
轉成 NSData
,而不是直接使用傳入的 imageData
,那麼就要按不一樣的圖片格式來轉成對應的 NSData
對象。
NSData 用來包裝數據,存儲的是二進制數據,屏蔽了數據之間的差別,文本、音頻、圖像等數據均可用NSData來存儲。
判斷圖片格式:根據是否有 alpha 通道以及 imageData
的前8位字節 判斷圖片格式詳解 若是 imageData 爲 nil,就根據 image 是否有 alpha 通道來判斷圖片是不是 PNG 格式的 若是 imageData 不爲 nil,就根據 imageData 的前 8 位字節來判斷是否是 PNG 格式,由於 PNG 圖片有一個惟一簽名,前 8 位字節是(十進制): 137 80 78 71 13 10 26 10
拿到 imageData 後,藉助 NSFileManager 將圖片二進制存儲到沙盒,存儲的文件名是對 key 進行 MD5 處理後生成的字符串。 默認沙盒路徑: Library - Caches
iOS的沙盒機制 SandBox 一種安全體制,規定應用程序只能在爲該應用建立的文件夾內讀取文件,不能訪問其餘地方的內容。保存全部的非代碼文件,如圖片,聲音,屬性列表和文本文件等。 應用程序向外請求或接收數據都須要通過權限認證。 默認狀況下,每一個沙盒含有3個文件夾:Documents, Library 和 tmp
queryDiskCacheForKey
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { } 複製代碼
返回的是一個 NSOperation 對象 這個方法會先讀取內存緩存,若是沒有再讀取磁盤緩存。 讀取磁盤緩存時,會先從沙盒中去找,若是沙盒中沒有,再從 customPaths
(也就是 bundle)中去找。 找到以後,對數據進行轉換,後面的圖片處理步驟跟圖片下載成功後的圖片處理步驟同樣——先將 data 轉成 image,再進行根據文件名中的 @2x、@3x 進行縮放處理,若是須要解壓縮,最後再解壓縮一下。
指標
- 緩存有效期:經過
maxCacheAge
屬性設置,默認一週- 緩存體積最大限制:經過
maxCacheSize
來設置的,默認爲 0。
SDImageCache
在初始化時添加了通知觀察者,因此在應用即將終止時和退到後臺時,都會調用 -cleanDiskWithCompletionBlock:
方法來異步清掃緩存。 清掃磁盤緩存(clean): 遍歷全部緩存文件,若是設置了 maxCacheAge
(最大緩存不過時時間) 屬性的話,先刪掉過時的文件,同時記錄文件的屬性和整體積大小,把文件按修改時間從早到晚排序,再遍歷這個文件數組,一個一個刪,直到整體積小於 desiredCacheSize 爲止,也就是 maxCacheSize 的一半。
主要任務
具體實現: +initialize
中主要是經過註冊通知 讓SDNetworkActivityIndicator
監聽下載事件,來顯示和隱藏狀態欄上的 network activity indicator。 爲了讓 SDNetworkActivityIndicator
文件能夠不用導入項目中來(若是不要的話),這裏使用了 runtime 的方式來實現動態建立類以及調用方法。
+ (void)initialize { if (NSClassFromString(@"SDNetworkActivityIndicator")) { id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")]; # 先移除通知觀察者 SDNetworkActivityIndicator # 再添加通知觀察者 SDNetworkActivityIndicator } } 複製代碼
+sharedDownloader
方法中調用了 -init
方法來建立一個單例,-init
方法中作了一些初始化設置和默認值設置,包括設置最大併發數(6)、下載超時時長(15s)等。
核心方法: - downloadImageWithURL: options: progress: completed:
方法 首先經過調用 -addProgressCallback: andCompletedBlock: forURL: createCallback:
方法來保存每一個 url 對應的回調 block -addProgressCallback: ...
方法先進行錯誤檢查,判斷 URL 是否爲空,而後再將 URL 對應的 progressBlock
和 completedBlock
保存到 URLCallbacks
屬性中。
URLCallbacks
屬性是一個 NSMutableDictionary
對象,key 是圖片的 URL,value 是一個數組,包含每一個圖片的多組回調信息。
由於可能同時下載多張圖片,因此就可能出現多個線程同時訪問 URLCallbacks
屬性的狀況。爲了保證線程安全,因此這裏使用了 dispatch_barrier_sync
來分步執行添加到 barrierQueue
中的任務,這樣就能保證同一時間只有一個線程能對 URLCallbacks
進行操做。
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback { //1. 判斷 url 是否爲 nil,若是爲 nil 則直接回調 completedBlock,返回失敗的結果,而後 return,由於 url 會做爲存儲 callbacks 的 key //2. 處理同一個 URL 的屢次下載請求(MARK: 使用 dispatch_barrier_sync 函數來保證同一時間只有一個線程能對 URLCallbacks 進行操做): //3. 從屬性 URLCallbacks(一個字典) 中取出對應 url 的 callBacksForURL(這是一個數組,由於可能一個 url 不止在一個地方下載) //4. 若是沒有取到,也就意味着這個 url 是第一次下載,那就初始化一個 callBacksForURL 放到屬性 URLCallbacks 中 //5. 往數組 callBacksForURL 中添加 包裝有 callbacks(progressBlock 和 completedBlock)的字典 //6. 更新 URLCallbacks 存儲的對應 url 的 callBacksForUR } 複製代碼
若是這個 URL 是第一次被下載,就要回調 createCallback
,createCallback
主要作的就是建立並開啓下載任務
createCallback
方法中調用了 - [SDWebImageDownloaderOperation initWithRequest: options: progress:]
方法來建立下載任務 SDWebImageDownloaderOperation
。
因爲 UIImage 的 imageWithData
函數是每次畫圖的時候纔將 Data 解壓成 ARGB 的圖像,因此在每次畫圖的時候都會有一個解壓操做,這樣雖然只有瞬時的內存需求,可是效率很低。 爲了提升效率,經過 SDWebImageDecoder 將包裝在 Data 下的資源畫在另一張圖片上,這樣這張新圖片就再也不須要重複解壓了,是空間換時間的作法。
圖片的解碼實際是將圖片的二進制數據轉換成像素數據的過程,SD 對圖片進行從新繪製,獲得一張位圖。 顯示圖片須要 RGBA 的色彩空間(什麼是 RGBA ?),可是 PNG 和 JPEG 自身的格式非 RGBA。因此建立一個 BitmapImage,先在非UI線程渲染圖片,做爲預解碼,而後拿到UIImage去顯示。 iOS 圖片解碼
能夠預先下載,可是下載是低優先級的。
State
operation 的執行過程: isReady -> isExecuting -> isFinished經過 keypath 的 KVO 通知來隱式的獲得 state ,而不是顯式的經過一個 state 的屬性。當一個 operation 已經準備就緒,將要被執行時,它會爲 isReadykeyPath 發送一個KVO的通知,對應的屬性值也會變爲YES.
爲了構造一致的狀態,每一個屬性都與其餘屬性相互排斥: isReady : 若是 operation 已經作好了執行的準備返回YES,若是它所依賴的操做存在一些未完成的初始化步驟則返回NO。 isExecuting :若是 operation 正在執行它的任務返回YES,不然返回NO。 isFinished : 任務成功的完成了執行,或者中途被 Cancel ,返回YES。
NSOperationQueue 只會把 isFinished 爲 YES 的 operation 踢出隊列, isFinished 爲 NO 的永遠不會被移除,因此實現時要保證其正確性,避免死鎖發生
Cancellation
取消一個 operation 的兩種狀況:
NSOperation 的被取消也是經過 isCancelledkeypath 的 KVO 來得到。當 NSOperation 的子類覆寫 cancel 方法時,注意清理掉內部分配的資源。 特別注意的是,這時 isCancelled 和 isFinished 的值都變爲了 YES, isExecuting 爲值變爲NO。
cancel : 帶一個」l」 表示方法 (動詞) isCancelled : 帶兩個」l」表示屬性(形容詞)
優先級 Priority
設置 queuePriority 屬性就能夠提高和下降 operation 的優先級, queuePriority 屬性可選的值以下: NSOperationQueuePriorityVeryHigh NSOperationQueuePriorityHigh NSOperationQueuePriorityNormal NSOperationQueuePriorityLow NSOperationQueuePriorityVeryLow 另外,operation 能夠指定一個 threadPriority 值,它的取值範圍是0.0到1.0,1.0表明最高的優先級。 queuePriority:決定執行順序的優先級 threadPriority:決定 operation 開始執行以後分配的計算資源的多少
依賴 Dependencies
若是須要把一個大的任務分紅多個子任務,可使用依賴,來保證前後執行順序。 B 操做若是依賴於 A,則必須在 A operation 的 isFinished 爲 YES 的時候纔會開始執行。 【避免循環依賴產生死鎖】
[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];
複製代碼
SDWebImageCombinedOperation 當 url 被正確傳入以後, 會實例一個很是奇怪的 「operation」, 它實際上是一個遵循 SDWebImageOperation 協議的 NSObject 的子類. 而這個協議也很是的簡單:
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end
複製代碼
這裏僅僅是將這個 SDWebImageOperation 類包裝成一個看着像 NSOperation 其實並非 NSOperation 的類, 而這個類惟一與 NSOperation 的相同之處就是它們均可以響應 cancel 方法. (不知道這句看似像繞口令的話, 你看懂沒有, 若是沒看懂..請多讀幾遍). 而調用這個類的存在實際是爲了使代碼更加的簡潔, 由於調用這個類的 cancel 方法, 會使得它持有的兩個 operation 都被 cancel.
// SDWebImageCombinedOperation // cancel #1 - (void)cancel { self.cancelled = YES; if (self.cacheOperation) { [self.cacheOperation cancel]; self.cacheOperation = nil; } if (self.cancelBlock) { self.cancelBlock(); _cancelBlock = nil; } } 複製代碼
而這個類, 應該是爲了實現更簡潔的 cancel 操做而設計出來的.
每張圖片的下載都會發出一個異步的 HTTP 請求,由 DownloaderOperation
管理。
DownloaderOperation
繼承 NSOperation
,遵照 SDWebImageOperation
、NSURLConnectionDataDelegate
協議。
SDWebImageOperation
協議只定義了一個方法 -cancel
,用來取消 operation。
當建立的 DownloaderOperation
對象被加入到 downloader
的 downloadQueue
中時,該對象的 -start
方法就會被自動調用。 -start
方法中首先建立了用來下載圖片數據的 NSURLConnection
,而後開啓 connection,同時發出開始圖片下載的 當圖片的全部數據下載完成後,Downloader
傳入的 completionBlock
被調用,圖片下載結束。
所以圖片的數據下載是由一個 NSConnection
對象來完成的,這個對象的整個生命週期(從建立到下載結束)是由 DownloaderOperation
來控制的,將 operation
加入到 operation queue
中就能夠實現多張圖片同時下載了
NS_OPTIONS 枚舉類型的使用 使用 NS_OPTIONS 位運算枚舉類型,可同時 經過「與」運算符,能夠判斷是否設置了某個枚舉選項,由於每一個枚舉選擇項中只有一位是1,其他位都是 0,因此只有參與運算的另外一個二進制值在一樣的位置上也爲 1,與 運算的結果纔不會爲 0. Eg. 0101 (至關於 SDWebImageDownloaderLowPriority | SDWebImageDownloaderUseNSURLCache) & 0100 (= 1 << 2,也就是 SDWebImageDownloaderUseNSURLCache) = 0100 (> 0,也就意味着 option 參數中設置了 SDWebImageDownloaderUseNSURLCache)
初始化 通常來講,一個管理類都有一個全局的單例對象,根據業務需求設計不一樣的初始化方法。在設計類的時候,應該經過合理的初始化方法告訴別的開發者,該類應該如何建立。
使用@synchronized: 在 Manager 對 failedURLs
和 runningOperations
作操做時均使用了@synchronized,在新版本里換成了 GCD 實現
下載高分辨率圖,致使內存暴增的解決辦法
FLAnimatedImage
來處理動圖SDImageCacheConfig
對緩存進行配置,能夠選擇是否解壓緩存、iCloud、最大緩存大小。sd_decompressedAndScaledDownImageWithImage:
避免要縮放的圖片太大,採用的方式是將圖片分割成一系列大小的小方塊,而後每一個方塊去獲取 Image 並 draw 到目標 BitmapContext 上對於緩存如何作優化?
(1). -> LRU -> LRU+FIFO
(2). 緩存模糊匹配 針對同一圖片,不一樣大小的請求。若是緩存中有更大的圖片,也視爲命中緩存。