前不久做了一個(gè)生成快照的需求卒密,其中用到 SDWebImage 來(lái)下載圖片揭绑,在使用該框架的過(guò)程中也遇到了一些問(wèn)題,索性正好就把 SDWebImage (v3.7.3) 源碼細(xì)讀了一下辞色,學(xué)習(xí)一下其中的設(shè)計(jì)思想和技術(shù)點(diǎn)锯厢,為了梳理思路书斜,順便寫下了這篇文章。
目錄
一、簡(jiǎn)介
1. 設(shè)計(jì)目的
SDWebImage
提供了 UIImageView
造寝、UIButton
、MKAnnotationView
的圖片下載分類吭练,只要一行代碼就可以實(shí)現(xiàn)圖片異步下載和緩存功能诫龙。這樣開發(fā)者就無(wú)須花太多精力在圖片下載細(xì)節(jié)上,專心處理業(yè)務(wù)邏輯鲫咽。
2. 特性
- 提供
UIImageView
,UIButton
,MKAnnotationView
的分類签赃,用來(lái)顯示網(wǎng)絡(luò)圖片谷异,以及緩存管理 - 異步下載圖片
- 異步緩存(內(nèi)存+磁盤),并且自動(dòng)管理緩存有效性
- 后臺(tái)圖片解壓縮
- 同一個(gè) URL 不會(huì)重復(fù)下載
- 自動(dòng)識(shí)別無(wú)效 URL锦聊,不會(huì)反復(fù)重試
- 不阻塞主線程
- 高性能
- 使用 GCD 和 ARC
- 支持多種圖片格式(包括 WebP 格式)
- 支持動(dòng)圖(GIF)
- 4.0 之前的動(dòng)圖效果并不是太好
- 4.0 以后基于 FLAnimatedImage加載動(dòng)圖
注:本文選讀的代碼是 3.7.3 版本的歹嘹,所以動(dòng)圖加載還不支持
FLAnimatedImage
。
3. SDWebImage 與其他框架的對(duì)比
利益相關(guān):以下兩篇文章都是 SDWebImage 的維護(hù)者所寫括丁,具有一定的主觀性荞下,僅供參考。
- How is SDWebImage better than X?
- iOS image caching. Libraries benchmark (SDWebImage vs FastImageCache)
4. 常見(jiàn)問(wèn)題
-
問(wèn)題 1:使用
UITableViewCell
中的imageView
加載不同尺寸的網(wǎng)絡(luò)圖片時(shí)會(huì)出現(xiàn)尺寸縮放問(wèn)題解決方案:自定義
UITableViewCell
史飞,重寫-layoutSubviews
方法尖昏,調(diào)整位置尺寸;或者直接棄用UITableViewCell
的imageView
构资,自己添加一個(gè) imageView 作為子控件抽诉。 -
問(wèn)題 2:圖片刷新問(wèn)題:
SDWebImage
在進(jìn)行緩存時(shí)忽略了所有服務(wù)器返回的 caching control 設(shè)置,并且在緩存時(shí)沒(méi)有做時(shí)間限制吐绵,這也就意味著圖片 URL 必須是靜態(tài)的了迹淌,要求服務(wù)器上一個(gè) URL 對(duì)應(yīng)的圖片內(nèi)容不允許更新。但是如果存儲(chǔ)圖片的服務(wù)器不由自己控制己单,也就是說(shuō) 圖片內(nèi)容更新了唉窃,URL 卻沒(méi)有更新,這種情況怎么辦纹笼?解決方案:在調(diào)用
sd_setImageWithURL: placeholderImage: options:
方法時(shí)設(shè)置 options 參數(shù)為SDWebImageRefreshCached
纹份,這樣雖然會(huì)降低性能,但是下載圖片時(shí)會(huì)照顧到服務(wù)器返回的 caching control廷痘。 -
問(wèn)題 3:在加載圖片時(shí)蔓涧,如何添加默認(rèn)的 progress indicator ?
解決方案:在調(diào)用
-sd_setImageWithURL:
方法之前笋额,先調(diào)用下面的方法:[imageView sd_setShowActivityIndicatorView:YES]; [imageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];
5. 用法
5.1 UITableView 中使用 UIImageView+WebCache
UITabelViewCell
中的 UIImageView
控件直接調(diào)用 sd_setImageWithURL: placeholderImage:
方法即可
5.2 使用回調(diào) blocks
在 block 中得到圖片下載進(jìn)度和圖片加載完成(下載完成或者讀取緩存)的回調(diào)元暴,如果你在圖片加載完成前取消了請(qǐng)求操作,就不會(huì)收到成功或失敗的回調(diào)
[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 ...
}];
5.3 SDWebImageManager 的使用
UIImageView(WebCache)
分類的核心在于 SDWebImageManager
的下載和緩存處理兄猩,SDWebImageManager
將圖片下載和圖片緩存組合起來(lái)了茉盏。SDWebImageManager
也可以單獨(dú)使用。
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
}
}];
5.4 單獨(dú)使用 SDWebImageDownloader 異步下載圖片
我們還可以單獨(dú)使用 SDWebImageDownloader
來(lái)下載圖片枢冤,但是圖片內(nèi)容不會(huì)緩存援岩。
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
}
}];
5.5 單獨(dú)使用 SDImageCache 異步緩存圖片
SDImageCache
支持內(nèi)存緩存和異步的磁盤緩存(可選),如果你想單獨(dú)使用 SDImageCache
來(lái)緩存數(shù)據(jù)的話掏导,可以使用單例享怀,也可以創(chuàng)建一個(gè)有獨(dú)立命名空間的 SDImageCache
實(shí)例。
添加緩存的方法:
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
默認(rèn)情況下趟咆,圖片數(shù)據(jù)會(huì)同時(shí)緩存到內(nèi)存和磁盤中添瓷,如果你想只要內(nèi)存緩存的話梅屉,可以使用下面的方法:
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey toDisk:NO];
讀取緩存時(shí)可以使用 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
}];
5.6 自定義緩存 key
有時(shí)候,一張圖片的 URL 中的一部分可能是動(dòng)態(tài)變化的(比如獲取權(quán)限上的限制)搀愧,所以我們只需要把 URL 中不變的部分作為緩存用的 key惰聂。
SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
};
6. SDWebImage 4.0 遷移指南
按照版本號(hào)慣例(Semantic Versioning),從版本號(hào)可以看出 SDWebImage 4.0 是一個(gè)大版本咱筛,在結(jié)構(gòu)上和 API 方面都有所改動(dòng)搓幌。
除了 iOS 和 tvOS 之外,SDWebImage 4.0 還支持更多的平臺(tái)——watchOS 和 Max OS X迅箩。
借助 FLAnimatedImage 在動(dòng)圖支持上做了改進(jìn)溉愁,尤其是 GIF。
二饲趋、實(shí)現(xiàn)原理
1. 架構(gòu)圖(UML 類圖)
2. 流程圖(方法調(diào)用順序圖)
3. 目錄結(jié)構(gòu)
- Downloader
SDWebImageDownloader
SDWebImageDownloaderOperation
- Cache
SDImageCache
- Utils
SDWebImageManager
SDWebImageDecoder
SDWebImagePrefetcher
- Categories
UIView+WebCacheOperation
UIImageView+WebCache
UIImageView+HighlightedWebCache
UIButton+WebCache
MKAnnotationView+WebCache
NSData+ImageContentType
UIImage+GIF
UIImage+MultiFormat
UIImage+WebP
- Other
-
SDWebImageOperation
(協(xié)議) -
SDWebImageCompat
(宏定義拐揭、常量、通用函數(shù))
-
類名 | 功能 |
---|---|
SDWebImageDownloader |
是專門用來(lái)下載圖片和優(yōu)化圖片加載的奕塑,跟緩存沒(méi)有關(guān)系 |
SDWebImageDownloaderOperation |
繼承于 NSOperation 堂污,用來(lái)處理下載任務(wù)的 |
SDImageCache |
用來(lái)處理內(nèi)存緩存和磁盤緩存(可選)的,其中磁盤緩存是異步進(jìn)行的龄砰,因此不會(huì)阻塞主線程 |
SDWebImageManager |
作為 UIImageView+WebCache 背后的默默付出者敷鸦,主要功能是將圖片下載(SDWebImageDownloader )和圖片緩存(SDImageCache )兩個(gè)獨(dú)立的功能組合起來(lái) |
SDWebImageDecoder |
圖片解碼器,用于圖片下載完成后進(jìn)行解碼 |
SDWebImagePrefetcher |
預(yù)下載圖片寝贡,方便后續(xù)使用,圖片下載的優(yōu)先級(jí)低值依,其內(nèi)部由 SDWebImageManager 來(lái)處理圖片下載和緩存 |
UIView+WebCacheOperation |
用來(lái)記錄圖片加載的 operation圃泡,方便需要時(shí)取消和移除圖片加載的 operation |
UIImageView+WebCache |
集成 SDWebImageManager 的圖片下載和緩存功能到 UIImageView 的方法中,方便調(diào)用方的簡(jiǎn)單使用 |
UIImageView+HighlightedWebCache |
跟 UIImageView+WebCache 類似愿险,也是包裝了 SDWebImageManager 颇蜡,只不過(guò)是用于加載 highlighted 狀態(tài)的圖片 |
UIButton+WebCache |
跟 UIImageView+WebCache 類似,集成 SDWebImageManager 的圖片下載和緩存功能到 UIButton 的方法中辆亏,方便調(diào)用方的簡(jiǎn)單使用 |
MKAnnotationView+WebCache |
跟 UIImageView+WebCache 類似 |
NSData+ImageContentType |
用于獲取圖片數(shù)據(jù)的格式(JPEG风秤、PNG等) |
UIImage+GIF |
用于加載 GIF 動(dòng)圖 |
UIImage+MultiFormat |
根據(jù)不同格式的二進(jìn)制數(shù)據(jù)轉(zhuǎn)成 UIImage 對(duì)象 |
UIImage+WebP |
用于解碼并加載 WebP 圖片 |
4. 核心邏輯
下載 Source code(3.7.3),運(yùn)行 pod install
扮叨,然后打開 SDWebImage.xcworkspace
缤弦,先 run 起來(lái)感受一下。
在了解細(xì)節(jié)之前我們先大概瀏覽一遍主流程彻磁,也就是最核心的邏輯碍沐。
我們從 MasterViewController
中的 [cell.imageView sd_setImageWithURL:url placeholderImage:placeholderImage];
開始看起狸捅。
經(jīng)過(guò)層層調(diào)用,直到 UIImageView+WebCache
中最核心的方法 sd_setImageWithURL: placeholderImage: options: progress: completed:
累提。該方法中尘喝,主要做了以下幾件事:
- 取消當(dāng)前正在進(jìn)行的加載任務(wù) operation
- 設(shè)置 placeholder
- 如果 URL 不為
nil
,就通過(guò)SDWebImageManager
單例開啟圖片加載任務(wù) operation斋陪,SDWebImageManager
的圖片加載方法中會(huì)返回一個(gè)SDWebImageCombinedOperation
對(duì)象朽褪,這個(gè)對(duì)象包含一個(gè) cacheOperation 和一個(gè) cancelBlock。
SDWebImageManager
的圖片加載方法 downloadImageWithURL:options:progress:completed:
中會(huì)先拿圖片緩存的 key (這個(gè) key 默認(rèn)是圖片 URL)去 SDImageCache
單例中讀取內(nèi)存緩存无虚,如果有缔赠,就返回給 SDWebImageManager
;如果內(nèi)存緩存沒(méi)有骑科,就開啟異步線程橡淑,拿經(jīng)過(guò) MD5 處理的 key 去讀取磁盤緩存,如果找到磁盤緩存了咆爽,就同步到內(nèi)存緩存中去梁棠,然后再返回給 SDWebImageManager
。
如果內(nèi)存緩存和磁盤緩存中都沒(méi)有斗埂,SDWebImageManager
就會(huì)調(diào)用 SDWebImageDownloader
單例的 -downloadImageWithURL: options: progress: completed:
方法去下載符糊,該會(huì)先將傳入的 progressBlock
和 completedBlock
保存起來(lái),并在第一次下載該 URL 的圖片時(shí)呛凶,創(chuàng)建一個(gè) NSMutableURLRequest
對(duì)象和一個(gè) SDWebImageDownloaderOperation
對(duì)象男娄,并將該 SDWebImageDownloaderOperation
對(duì)象添加到 SDWebImageDownloader
的downloadQueue
來(lái)啟動(dòng)異步下載任務(wù)。
SDWebImageDownloaderOperation
中包裝了一個(gè) NSURLConnection
的網(wǎng)絡(luò)請(qǐng)求漾稀,并通過(guò) runloop 來(lái)保持 NSURLConnection
在 start 后模闲、收到響應(yīng)前不被干掉,下載圖片時(shí)崭捍,監(jiān)聽(tīng) NSURLConnection
回調(diào)的 -connection:didReceiveData:
方法中會(huì)負(fù)責(zé) progress 相關(guān)的處理和回調(diào)尸折,- connectionDidFinishLoading:
方法中會(huì)負(fù)責(zé)將 data 轉(zhuǎn)為 image,以及圖片解碼操作殷蛇,并最終回調(diào) completedBlock实夹。
SDWebImageDownloaderOperation
中的圖片下載請(qǐng)求完成后,會(huì)回調(diào)給 SDWebImageDownloader
粒梦,然后 SDWebImageDownloader
再回調(diào)給 SDWebImageManager
亮航,SDWebImageManager
中再將圖片分別緩存到內(nèi)存和磁盤上(可選),并回調(diào)給 UIImageView
匀们,UIImageView
中再回到主線程設(shè)置 image
屬性缴淋。至此,圖片的下載和緩存操作就圓滿結(jié)束了。
當(dāng)然宴猾,SDWebImage
中還有很多細(xì)節(jié)可以深挖圆存,包括一些巧妙設(shè)計(jì)和知識(shí)點(diǎn),接下來(lái)再看看SDWebImage
中的實(shí)現(xiàn)細(xì)節(jié)仇哆。
三沦辙、實(shí)現(xiàn)細(xì)節(jié)
注:為了節(jié)省篇幅,這里使用偽代碼的方式來(lái)解讀讹剔,具體的閱讀注解見(jiàn) ShannonChenCHN/SDWebImage-3.7.3油讯。
從上面的核心邏輯分析可以看出,SDWebImage
最核心的功能也就是以下 4 件事:
- 下載(
SDWebImageDownloader
) - 緩存(
SDImageCache
) - 將緩存和下載的功能組合起來(lái)(
SDWebImageManager
) - 封裝成 UIImageView 等類的分類方法(
UIImageView+WebCache
等)
1. 圖片下載
1.1 SDWebImageDownloader
SDWebImageDownloader
繼承于 NSObject
延欠,主要承擔(dān)了異步下載圖片和優(yōu)化圖片加載的任務(wù)陌兑。
幾個(gè)問(wèn)題
- 如何實(shí)現(xiàn)異步下載,也就是多張圖片同時(shí)下載由捎?
- 如何處理同一張圖片(同一個(gè) URL)多次下載的情況兔综?
枚舉定義
// 下載選項(xiàng)
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
SDWebImageDownloaderUseNSURLCache = 1 << 2,
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
SDWebImageDownloaderContinueInBackground = 1 << 4,
SDWebImageDownloaderHandleCookies = 1 << 5,
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
SDWebImageDownloaderHighPriority = 1 << 7,
};
// 下載任務(wù)執(zhí)行順序
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
SDWebImageDownloaderFIFOExecutionOrder, // 先進(jìn)先出
SDWebImageDownloaderLIFOExecutionOrder // 后進(jìn)先出
};
.h 文件中的屬性:
@property (assign, nonatomic) BOOL shouldDecompressImages; // 下載完成后是否需要解壓縮圖片,默認(rèn)為 YES
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
@property (strong, nonatomic) NSString *username;
@property (strong, nonatomic) NSString *password;
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;
.m 文件中的屬性:
@property (strong, nonatomic) NSOperationQueue *downloadQueue; // 圖片下載任務(wù)是放在這個(gè) NSOperationQueue 任務(wù)隊(duì)列中來(lái)管理的
@property (weak, nonatomic) NSOperation *lastAddedOperation;
@property (assign, nonatomic) Class operationClass;
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks; // 圖片下載的回調(diào) block 都是存儲(chǔ)在這個(gè)屬性中狞玛,該屬性是一個(gè)字典软驰,key 是圖片的 URL,value 是一個(gè)數(shù)組心肪,包含每個(gè)圖片的多組回調(diào)信息锭亏。用 JSON 格式表示的話,就是下面這種形式:
.h 文件中方法
+ (SDWebImageDownloader *)sharedDownloader;
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
- (void)setOperationClass:(Class)operationClass; // 創(chuàng)建 operation 用的類
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (void)setSuspended:(BOOL)suspended;
.m 文件中的方法
// Lifecycle
+ (void)initialize;
+ (SDWebImageDownloader *)sharedDownloader;
- init;
- (void)dealloc;
// Setter and getter
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads;
- (NSUInteger)currentDownloadCount;
- (NSInteger)maxConcurrentDownloads;
- (void)setOperationClass:(Class)operationClass;
// Download
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(NSURL *)url
createCallback:(SDWebImageNoParamsBlock)createCallback;
// Download queue
- (void)setSuspended:(BOOL)suspended;
具體實(shí)現(xiàn):
先看看 +initialize
方法硬鞍,這個(gè)方法中主要是通過(guò)注冊(cè)通知 讓SDNetworkActivityIndicator
監(jiān)聽(tīng)下載事件慧瘤,來(lái)顯示和隱藏狀態(tài)欄上的 network activity indicator。為了讓 SDNetworkActivityIndicator
文件可以不用導(dǎo)入項(xiàng)目中來(lái)(如果不要的話)固该,這里使用了 runtime 的方式來(lái)實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建類以及調(diào)用方法锅减。
+ (void)initialize {
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
# 先移除通知觀察者 SDNetworkActivityIndicator
# 再添加通知觀察者 SDNetworkActivityIndicator
}
}
+sharedDownloader
方法中調(diào)用了 -init
方法來(lái)創(chuàng)建一個(gè)單例,-init
方法中做了一些初始化設(shè)置和默認(rèn)值設(shè)置,包括設(shè)置最大并發(fā)數(shù)(6)、下載超時(shí)時(shí)長(zhǎng)(15s)等藏斩。
- (id)init {
#設(shè)置下載 operation 的默認(rèn)執(zhí)行順序(先進(jìn)先出還是先進(jìn)后出)
#初始化 _downloadQueue(下載隊(duì)列),_URLCallbacks(下載回調(diào) block 的容器),_barrierQueue(GCD 隊(duì)列)
#設(shè)置 _downloadQueue 的隊(duì)列最大并發(fā)數(shù)默認(rèn)值為 6
#設(shè)置 _HTTPHeaders 默認(rèn)值
#設(shè)置默認(rèn)下載超時(shí)時(shí)長(zhǎng) 15s
...
}
除了以上兩個(gè)方法之外拴疤,這個(gè)類中最核心的方法就是 - downloadImageWithURL: options: progress: completed:
方法永部,這個(gè)方法中首先通過(guò)調(diào)用 -addProgressCallback: andCompletedBlock: forURL: createCallback:
方法來(lái)保存每個(gè) url 對(duì)應(yīng)的回調(diào) block,-addProgressCallback: ...
方法先進(jìn)行錯(cuò)誤檢查呐矾,判斷 URL 是否為空苔埋,然后再將 URL 對(duì)應(yīng)的 progressBlock
和 completedBlock
保存到 URLCallbacks
屬性中去。
URLCallbacks
屬性是一個(gè) NSMutableDictionary
對(duì)象蜒犯,key 是圖片的 URL组橄,value 是一個(gè)數(shù)組荞膘,包含每個(gè)圖片的多組回調(diào)信息。用 JSON 格式表示的話玉工,就是下面這種形式:
{
"callbacksForUrl1": [
{
"kProgressCallbackKey": "progressCallback1_1",
"kCompletedCallbackKey": "completedCallback1_1"
},
{
"kProgressCallbackKey": "progressCallback1_2",
"kCompletedCallbackKey": "completedCallback1_2"
}
],
"callbacksForUrl2": [
{
"kProgressCallbackKey": "progressCallback2_1",
"kCompletedCallbackKey": "completedCallback2_1"
},
{
"kProgressCallbackKey": "progressCallback2_2",
"kCompletedCallbackKey": "completedCallback2_2"
}
]
}
這里有個(gè)細(xì)節(jié)需要注意羽资,因?yàn)榭赡芡瑫r(shí)下載多張圖片,所以就可能出現(xiàn)多個(gè)線程同時(shí)訪問(wèn) URLCallbacks
屬性的情況遵班。為了保證線程安全屠升,所以這里使用了 dispatch_barrier_sync
來(lái)分步執(zhí)行添加到 barrierQueue
中的任務(wù),這樣就能保證同一時(shí)間只有一個(gè)線程能對(duì) URLCallbacks
進(jìn)行操作狭郑。
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
#1. 判斷 url 是否為 nil腹暖,如果為 nil 則直接回調(diào) completedBlock,返回失敗的結(jié)果翰萨,然后 return脏答,因?yàn)?url 會(huì)作為存儲(chǔ) callbacks 的 key
#2. 處理同一個(gè) URL 的多次下載請(qǐng)求(MARK: 使用 dispatch_barrier_sync 函數(shù)來(lái)保證同一時(shí)間只有一個(gè)線程能對(duì) URLCallbacks 進(jìn)行操作):
## 從屬性 URLCallbacks(一個(gè)字典) 中取出對(duì)應(yīng) url 的 callBacksForURL(這是一個(gè)數(shù)組,因?yàn)榭赡芤粋€(gè) url 不止在一個(gè)地方下載)
## 如果沒(méi)有取到亩鬼,也就意味著這個(gè) url 是第一次下載殖告,那就初始化一個(gè) callBacksForURL 放到屬性 URLCallbacks 中
## 往數(shù)組 callBacksForURL 中添加 包裝有 callbacks(progressBlock 和 completedBlock)的字典
## 更新 URLCallbacks 存儲(chǔ)的對(duì)應(yīng) url 的 callBacksForURL
#3. 如果這個(gè) url 是第一次請(qǐng)求下載,就回調(diào) createCallback
}
如果這個(gè) URL 是第一次被下載辛孵,就要回調(diào) createCallback
丛肮,createCallback
主要做的就是創(chuàng)建并開啟下載任務(wù),下面是 createCallback
的具體實(shí)現(xiàn)邏輯:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
#1. 調(diào)用 - [SDWebImageDownloader addProgressCallback: andCompletedBlock: forURL: createCallback: ] 方法魄缚,直接把入?yún)?url宝与、progressBlock 和 completedBlock 傳進(jìn)該方法,并在第一次下載該 URL 時(shí)回調(diào) createCallback
## createCallback 的回調(diào)處理:{
1.1 創(chuàng)建下載 request 冶匹,設(shè)置 request 的 cachePolicy习劫、HTTPShouldHandleCookies、HTTPShouldUsePipelining嚼隘,以及 allHTTPHeaderFields(這個(gè)屬性交由外面處理诽里,設(shè)計(jì)的比較巧妙)
1.2 創(chuàng)建 SDWebImageDownloaderOperation(繼承自 NSOperation)
### 1.2.1 SDWebImageDownloaderOperation 的 progressBlock 回調(diào)處理 {
(這個(gè) block 有兩個(gè)回調(diào)參數(shù):接收到的數(shù)據(jù)大小和預(yù)計(jì)數(shù)據(jù)大小)
這里用了 weak-strong dance
首先使用 strongSelf 強(qiáng)引用 weakSelf飞蛹,目的是為了保住 self 不被釋放
然后檢查 self 是否已經(jīng)被釋放(這里為什么先“卑疲活”后“判空”呢?因?yàn)槿绻扰锌盏脑捨蚤埽锌赡芘锌蘸?self 就被釋放了)
取出 url 對(duì)應(yīng)的回調(diào) block 數(shù)組(這里取的時(shí)候有些講究墓懂,考慮了多線程問(wèn)題,而且取的是 copy 的內(nèi)容)
遍歷數(shù)組霉囚,從每個(gè)元素(字典)中取出 progressBlock 進(jìn)行回調(diào)
}
### 1.2.2 SDWebImageDownloaderOperation 的 completedBlock 回調(diào)處理 {
(這個(gè) block 有四個(gè)回調(diào)參數(shù):圖片 UIImage捕仔,圖片數(shù)據(jù) NSData,錯(cuò)誤 NSError,是否結(jié)束 isFinished)
同樣榜跌,這里也用了 weak-strong dance
接著闪唆,取出 url 對(duì)應(yīng)的回調(diào) block 數(shù)組
如果結(jié)束了(isFinished),就移除 url 對(duì)應(yīng)的回調(diào) block 數(shù)組(移除的時(shí)候也要考慮多線程問(wèn)題)
遍歷數(shù)組钓葫,從每個(gè)元素(字典)中取出 completedBlock 進(jìn)行回調(diào)
}
### SDWebImageDownloaderOperation 的 cancelBlock 回調(diào)處理 {
同樣悄蕾,這里也用了 weak-strong dance
然后移除 url 對(duì)應(yīng)的所有回調(diào) block
}
1.3 設(shè)置下載完成后是否需要解壓縮
1.4 如果設(shè)置了 username 和 password,就給 operation 的下載請(qǐng)求設(shè)置一個(gè) NSURLCredential
1.5 設(shè)置 operation 的隊(duì)列優(yōu)先級(jí)
1.6 將 operation 加入到隊(duì)列 downloadQueue 中瓤逼,隊(duì)列(NSOperationQueue)會(huì)自動(dòng)管理 operation 的執(zhí)行
1.7 如果 operation 執(zhí)行順序是先進(jìn)后出笼吟,就設(shè)置 operation 依賴關(guān)系(先加入的依賴于后加入的),并記錄最后一個(gè) operation(lastAddedOperation)
}
#2. 返回 createCallback 中創(chuàng)建的 operation(SDWebImageDownloaderOperation)
}
createCallback
方法中調(diào)用了 - [SDWebImageDownloaderOperation initWithRequest: options: progress:]
方法來(lái)創(chuàng)建下載任務(wù) SDWebImageDownloaderOperation
霸旗。那么贷帮,這個(gè) SDWebImageDownloaderOperation
類究竟是干什么的呢?下一節(jié)再看诱告。
知識(shí)點(diǎn):
- SDWebImageDownloaderOptions 枚舉使用了位運(yùn)算
應(yīng)用:通過(guò)“與”運(yùn)算符撵枢,可以判斷是否設(shè)置了某個(gè)枚舉選項(xiàng),因?yàn)槊總€(gè)枚舉選擇項(xiàng)中只有一位是1精居,其余位都是 0锄禽,所以只有參與運(yùn)算的另一個(gè)二進(jìn)制值在同樣的位置上也為 1,與 運(yùn)算的結(jié)果才不會(huì)為 0.0101 (相當(dāng)于 SDWebImageDownloaderLowPriority | SDWebImageDownloaderUseNSURLCache) & 0100 (= 1 << 2靴姿,也就是 SDWebImageDownloaderUseNSURLCache) = 0100 (> 0沃但,也就意味著 option 參數(shù)中設(shè)置了 SDWebImageDownloaderUseNSURLCache)
-
dispatch_barrier_sync
函數(shù)的使用 - weak-strong dance
- HTTP header 的理解
-
NSOperationQueue
的使用 -
NSURLRequest
的cachePolicy
、HTTPShouldHandleCookies
佛吓、HTTPShouldUsePipelining
NSURLCredential
-
createCallback
里面為什么要用 wself宵晚?NSTimeInterval timeoutInterval = wself.downloadTimeout;
1.2 SDWebImageDownloaderOperation
每張圖片的下載都會(huì)發(fā)出一個(gè)異步的 HTTP 請(qǐng)求,這個(gè)請(qǐng)求就是由 SDWebImageDownloaderOperation
管理的维雇。
SDWebImageDownloaderOperation
繼承 NSOperation
淤刃,遵守 SDWebImageOperation
、NSURLConnectionDataDelegate
協(xié)議吱型。
SDWebImageOperation
協(xié)議只定義了一個(gè)方法 -cancel
逸贾,用來(lái)取消 operation。
幾個(gè)問(wèn)題
- 如何實(shí)現(xiàn)下載的網(wǎng)絡(luò)請(qǐng)求津滞?
- 如何管理整個(gè)圖片下載的過(guò)程铝侵?
- 圖片下載完成后需要做哪些處理?
.h 文件中的屬性:
@property (strong, nonatomic, readonly) NSURLRequest *request; // 用來(lái)給 operation 中的 connection 使用的請(qǐng)求
@property (assign, nonatomic) BOOL shouldDecompressImages; // 下載完成后是否需要解壓縮
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
@property (nonatomic, strong) NSURLCredential *credential;
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
@property (assign, nonatomic) NSInteger expectedSize;
@property (strong, nonatomic) NSURLResponse *response;
其他繼承自 NSOperation 的屬性(略)
.m 文件中的屬性:
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (assign, nonatomic, getter = isExecuting) BOOL executing; // 覆蓋了 NSOperation 的 executing
@property (assign, nonatomic, getter = isFinished) BOOL finished; // 覆蓋了 NSOperation 的 finished
@property (assign, nonatomic) NSInteger expectedSize;
@property (strong, nonatomic) NSMutableData *imageData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, atomic) NSThread *thread;
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; // Xcode 的 BaseSDK 設(shè)置為 iOS 4.0 時(shí)以上使用
// 成員變量
size_t width, height; // 圖片寬高
UIImageOrientation orientation; // 圖片方向
BOOL responseFromCached;
.h 文件中的方法:
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
其他繼承自 NSOperation 的方法(略)
.m 文件中的方法:
// 覆蓋了父類的屬性触徐,需要重新實(shí)現(xiàn)屬性合成方法
@synthesize executing = _executing;
@synthesize finished = _finished;
// Initialization
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
// Operation
- (void)start;
- (void)cancel;
- (void)cancelInternalAndStop;
- (void)cancelInternal;
- (void)done;
- (void)reset;
// Setter and getter
- (void)setFinished:(BOOL)finished;
- (void)setExecuting:(BOOL)executing;
- (BOOL)isConcurrent;
// NSURLConnectionDataDelegate 方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; // 下載過(guò)程中的 response 回調(diào)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data; // 下載過(guò)程中 data 回調(diào)
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection; // 下載完成時(shí)回調(diào)
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; // 下載失敗時(shí)回調(diào)
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse; // 在 connection 存儲(chǔ) cached response 到緩存中之前調(diào)用
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection; // URL loader 是否應(yīng)該使用 credential storage
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; // connection 發(fā)送身份認(rèn)證的請(qǐng)求之前被調(diào)用
// Helper
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value;
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image;
- (BOOL)shouldContinueWhenAppEntersBackground;
具體實(shí)現(xiàn):
首先來(lái)看看指定初始化方法 -initWithRequest:options:progress:completed:cancelled:
咪鲜,這個(gè)方法是保存一些傳入的參數(shù),設(shè)置一些屬性的初始默認(rèn)值锌介。
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
# 接受參數(shù)嗜诀,設(shè)置屬性
# 設(shè)置屬性_shouldUseCredentialStorage、_executing孔祸、_finished隆敢、_expectedSize、responseFromCached 的默認(rèn)值/初始值
}
當(dāng)創(chuàng)建的 SDWebImageDownloaderOperation
對(duì)象被加入到 downloader 的 downloadQueue 中時(shí)崔慧,該對(duì)象的 -start
方法就會(huì)被自動(dòng)調(diào)用拂蝎。
-start
方法中首先創(chuàng)建了用來(lái)下載圖片數(shù)據(jù)的 NSURLConnection
,然后開啟 connection惶室,同時(shí)發(fā)出開始圖片下載的 SDWebImageDownloadStartNotification
通知温自,為了防止非主線程的請(qǐng)求被 kill 掉,這里開啟 runloop 被食活悼泌,直到請(qǐng)求返回。
- (void)start {
# 給 `self` 加鎖 {
## 如果 `self` 被 cancell 掉的話夹界,finished 屬性變?yōu)?YES馆里,reset 下載數(shù)據(jù)和回調(diào) block,然后直接 return可柿。
## 如果允許程序退到后臺(tái)后繼續(xù)下載鸠踪,就標(biāo)記為允許后臺(tái)執(zhí)行,在后臺(tái)任務(wù)過(guò)期的回調(diào) block 中 {
首先來(lái)一個(gè) weak-strong dance
調(diào)用 cancel 方法(這個(gè)方法里面又做了一些處理复斥,反正就是 cancel 掉當(dāng)前的 operation)
調(diào)用 UIApplication 的 endBackgroundTask: 方法結(jié)束任務(wù)
記錄結(jié)束后的 taskId
}
## 標(biāo)記 executing 屬性為 YES
## 創(chuàng)建 connection营密,賦值給 connection 屬性
## 獲取 currentThread,賦值給 thread 屬性
}
# 啟動(dòng) connection
# 因?yàn)樯厦娉跏蓟?connection 時(shí)可能會(huì)失敗目锭,所以這里我們需要根據(jù)不同情況做處理
## A.如果 connection 不為 nil
### 回調(diào) progressBlock(初始的 receivedSize 為 0评汰,expectSize 為 -1)
### 發(fā)出 SDWebImageDownloadStartNotification 通知(SDWebImageDownloader 會(huì)監(jiān)聽(tīng)到)
### 開啟 runloop
### runloop 結(jié)束后繼續(xù)往下執(zhí)行(也就是 cancel 掉或者 NSURLConnection 請(qǐng)求完畢代理回調(diào)后調(diào)用了 CFRunLoopStop)
## B.如果 connection 為 nil,回調(diào) completedBlock侣集,返回 connection 初始化失敗的錯(cuò)誤信息
# 下載完成后键俱,調(diào)用 endBackgroundTask: 標(biāo)記后臺(tái)任務(wù)結(jié)束
}
NSURLConnection
請(qǐng)求圖片數(shù)據(jù)時(shí),服務(wù)器返回的的結(jié)果是通過(guò) NSURLConnectionDataDelegate
的代理方法回調(diào)的世分,其中最主要的是以下三個(gè)方法:
- connection:didReceiveResponse: // 下載過(guò)程中的 response 回調(diào)编振,調(diào)用一次
- connection:didReceiveData: // 下載過(guò)程中 data 回調(diào),調(diào)用多次
- connectionDidFinishLoading: // 下載完成時(shí)回調(diào)臭埋,調(diào)用一次
前兩個(gè)方法是在下載過(guò)程中回調(diào)的踪央,第三個(gè)方法是在下載完成時(shí)回調(diào)的。第一個(gè)方法 - connection:didReceiveResponse:
被調(diào)用后瓢阴,接著會(huì)多次調(diào)用 - connection:didReceiveData:
方法來(lái)更新進(jìn)度畅蹂、拼接圖片數(shù)據(jù),當(dāng)圖片數(shù)據(jù)全部下載完成時(shí)荣恐,- connectionDidFinishLoading:
方法就會(huì)被調(diào)用液斜。
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
#A. 返回 code 不是 304 Not Modified
1. 獲取 expectedSize累贤,回調(diào) progressBlock
2. 初始化 imageData 屬性
3. 發(fā)送 SDWebImageDownloadReceiveResponseNotification 通知
#B. 針對(duì) 304 Not Modified 做處理,直接 cancel operation少漆,并返回緩存的 image
1. 取消連接
2. 發(fā)送 SDWebImageDownloadStopNotification 通知
3. 回調(diào) completedBlock
4. 停止 runloop
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
# 1.拼接圖片數(shù)據(jù)
# 2.針對(duì) `SDWebImageDownloaderProgressiveDownload` 做的處理
## 2.1 根據(jù)更新的 imageData 創(chuàng)建 CGImageSourceRef 對(duì)象
## 2.2 首次獲取到數(shù)據(jù)時(shí)臼膏,讀取圖片屬性:width, height, orientation
## 2.3 圖片還沒(méi)下載完,但不是第一次拿到數(shù)據(jù)示损,使用現(xiàn)有圖片數(shù)據(jù) CGImageSourceRef 創(chuàng)建 CGImageRef 對(duì)象
## 2.4 對(duì)圖片進(jìn)行縮放渗磅、解碼,回調(diào) completedBlock
# 3.回調(diào) progressBlock
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
# 1. 下載結(jié)束检访,停止 runloop始鱼,發(fā)送 SDWebImageDownloadStopNotification 通知和 SDWebImageDownloadFinishNotification 通知
# 2. 回調(diào) completionBlock
# 2.1 如果是返回的結(jié)果是 URL Cache,就回調(diào)圖片數(shù)據(jù)為 nil 的 completionBlock
# 2.2 如果有圖片數(shù)據(jù)
# 2.2.1 針對(duì)不同圖片格式進(jìn)行數(shù)據(jù)轉(zhuǎn)換 data -> image
# 2.2.2 據(jù)圖片名中是否帶 @2x 和 @3x 來(lái)做 scale 處理
# 2.2.3 如果需要解碼脆贵,就進(jìn)行圖片解碼(如果不是 GIF 圖)
# 2.2.4 判斷圖片尺寸是否為空医清,并回調(diào) completionBlock
# 2.3 如果沒(méi)有圖片數(shù)據(jù),回調(diào)帶有錯(cuò)誤信息的 completionBlock
# 3. 將 completionBlock 置為 nil
# 4. 重置
}
當(dāng)圖片的所有數(shù)據(jù)下載完成后卖氨,SDWebImageDownloader
傳入的 completionBlock
被調(diào)用状勤,至此,整個(gè)圖片的下載過(guò)程就結(jié)束了双泪。從上面的解讀中我們可以看到持搜,一張圖片的數(shù)據(jù)下載是由一個(gè) NSConnection
對(duì)象來(lái)完成的,這個(gè)對(duì)象的整個(gè)生命周期(從創(chuàng)建到下載結(jié)束)又是由 SDWebImageDownloaderOperation
來(lái)控制的焙矛,將 operation 加入到 operation queue 中就可以實(shí)現(xiàn)多張圖片同時(shí)下載了葫盼。
簡(jiǎn)單概括成一句話就是,NSConnection
負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求村斟,NSOperation
負(fù)責(zé)多線程贫导。
知識(shí)點(diǎn)
-
NSOperation
的-start
方法、-main
方法和-cancel
方法 -
-start
方法中為什么要調(diào)用CFRunLoopRun()
或者CFRunLoopRunInMode()
函數(shù)蟆盹?
參考:
- http://stanoz-io.top/2016/05/17/NSRunLoop_Note/
- http://tom555cat.com/2016/08/01/SdWebImage之RunLoop/
- http://blog.ibireme.com/2015/05/18/runloop/
- https://github.com/rs/SDWebImage/issues/497
- https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
SDWebImageDownloaderOperation
中是什么時(shí)候開啟異步線程的孩灯?NSURLConnection
的幾個(gè)代理方法分別在什么時(shí)候調(diào)用?NSURLCache
是什么逾滥?下載完成后峰档,為什么需要對(duì)圖片進(jìn)行解壓縮操作?
WebP 圖片的解碼
2. 圖片緩存——SDImageCache
首先我們想一想寨昙,為什么需要緩存讥巡?
- 以空間換時(shí)間,提升用戶體驗(yàn):加載同一張圖片舔哪,讀取緩存是肯定比遠(yuǎn)程下載的速度要快得多的
- 減少不必要的網(wǎng)絡(luò)請(qǐng)求欢顷,提升性能,節(jié)省流量:一般來(lái)講捉蚤,同一張圖片的 URL 是不會(huì)經(jīng)常變化的抬驴,所以沒(méi)有必要重復(fù)下載炼七。另外,現(xiàn)在的手機(jī)存儲(chǔ)空間都比較大布持,相對(duì)于流量來(lái)特石,緩存占的那點(diǎn)空間算不了什么
SDImageCache
管理著一個(gè)內(nèi)存緩存和磁盤緩存(可選),同時(shí)在寫入磁盤緩存時(shí)采取異步執(zhí)行鳖链,所以不會(huì)阻塞主線程,影響用戶體驗(yàn)墩莫。
幾個(gè)問(wèn)題
- 從讀取速度和保存時(shí)間上來(lái)考慮芙委,緩存該怎么存?key 怎么定狂秦?
- 內(nèi)存緩存怎么存灌侣?
- 磁盤緩存怎么存?路徑裂问、文件名怎么定侧啼?
- 使用時(shí)怎么讀取緩存?
- 什么時(shí)候需要移除緩存堪簿?怎么移除痊乾?
枚舉
typedef NS_ENUM(NSInteger, SDImageCacheType) {
SDImageCacheTypeNone, // 沒(méi)有讀取到圖片緩存,需要從網(wǎng)上下載
SDImageCacheTypeDisk, // 磁盤中的緩存
SDImageCacheTypeMemory // 內(nèi)存中的緩存
};
.h 文件中的屬性:
@property (assign, nonatomic) BOOL shouldDecompressImages; // 讀取磁盤緩存后椭更,是否需要對(duì)圖片進(jìn)行解壓縮
@property (assign, nonatomic) NSUInteger maxMemoryCost; // 其實(shí)就是 NSCache 的 totalCostLimit哪审,內(nèi)存緩存總消耗的最大限制,cost 是根據(jù)內(nèi)存中的圖片的像素大小來(lái)計(jì)算的
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit; // 其實(shí)就是 NSCache 的 countLimit虑瀑,內(nèi)存緩存的最大數(shù)目
@property (assign, nonatomic) NSInteger maxCacheAge; // 磁盤緩存的最大時(shí)長(zhǎng)湿滓,也就是說(shuō)緩存存多久后需要?jiǎng)h掉
@property (assign, nonatomic) NSUInteger maxCacheSize; // 磁盤緩存文件總體積最大限制,以 bytes 來(lái)計(jì)算
.m 文件中的屬性:
@property (strong, nonatomic) NSCache *memCache;
@property (strong, nonatomic) NSString *diskCachePath;
@property (strong, nonatomic) NSMutableArray *customPaths; // // 只讀的路徑舌狗,比如 bundle 中的文件路徑叽奥,用來(lái)在 SDWebImage 下載、讀取緩存之前預(yù)加載用的
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
NSFileManager *_fileManager;
.h 文件中的方法:
+ (SDImageCache *)sharedImageCache;
- (id)initWithNamespace:(NSString *)ns;
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
- (void)addReadOnlyCachePath:(NSString *)path;
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)clearMemory;
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
- (void)clearDisk;
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
- (void)cleanDisk;
- (NSUInteger)getSize;
- (NSUInteger)getDiskCount;
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (BOOL)diskImageExistsWithKey:(NSString *)key;
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
- (NSString *)defaultCachePathForKey:(NSString *)key;
.m 文件中的方法和函數(shù):
- 方法
// Lifecycle
+ (SDImageCache *)sharedImageCache;
- (id)init;
- (id)initWithNamespace:(NSString *)ns;
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
- (void)dealloc;
// Cache Path
- (void)addReadOnlyCachePath:(NSString *)path; // 添加只讀路徑,比如 bundle 中的文件路徑抬闷,用來(lái)在 SDWebImage 下載垂寥、讀取緩存之前預(yù)加載用的
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
- (NSString *)defaultCachePathForKey:(NSString *)key;
- (NSString *)cachedFileNameForKey:(NSString *)key
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
// Store Image
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
// Check if image exists
- (BOOL)diskImageExistsWithKey:(NSString *)key;
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
// Query the image cache
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
- (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key;
- (UIImage *)diskImageForKey:(NSString *)key;
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image;
// Remove specified image
- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
// Setter and getter
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost;
- (NSUInteger)maxMemoryCost;
- (NSUInteger)maxMemoryCountLimit;
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit;
// Clear and clean
- (void)clearMemory;
- (void)clearDisk;
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
- (void)cleanDisk;
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
- (void)backgroundCleanDisk;
// Cache Size
- (NSUInteger)getSize;
- (NSUInteger)getDiskCount;
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
2.函數(shù)
NSUInteger SDCacheCostForImage(UIImage *image);
BOOL ImageDataHasPNGPreffix(NSData *data);
具體實(shí)現(xiàn):
SDImageCache
的內(nèi)存緩存是通過(guò)一個(gè)繼承 NSCache
的 AutoPurgeCache
類來(lái)實(shí)現(xiàn)的,NSCache
是一個(gè)類似于 NSMutableDictionary
存儲(chǔ) key-value 的容器膀篮,主要有以下幾個(gè)特點(diǎn):
- 自動(dòng)刪除機(jī)制:當(dāng)系統(tǒng)內(nèi)存緊張時(shí),
NSCache
會(huì)自動(dòng)刪除一些緩存對(duì)象 - 線程安全:從不同線程中對(duì)同一個(gè)
NSCache
對(duì)象進(jìn)行增刪改查時(shí)岂膳,不需要加鎖 - 不同于
NSMutableDictionary
誓竿,NSCache
存儲(chǔ)對(duì)象時(shí)不會(huì)對(duì) key 進(jìn)行 copy 操作
SDImageCache
的磁盤緩存是通過(guò)異步操作 NSFileManager
存儲(chǔ)緩存文件到沙盒來(lái)實(shí)現(xiàn)的。
- 初始化
-init
方法中默認(rèn)調(diào)用了 -initWithNamespace:
方法谈截,-initWithNamespace:
方法又調(diào)用了 -makeDiskCachePath:
方法來(lái)初始化緩存目錄路徑筷屡, 同時(shí)還調(diào)用了 -initWithNamespace:diskCacheDirectory:
方法來(lái)實(shí)現(xiàn)初始化涧偷。下面是初始化方法調(diào)用棧:
-init
-initWithNamespace:
-makeDiskCachePath:
-initWithNamespace:diskCacheDirectory:
-initWithNamespace:diskCacheDirectory:
是一個(gè) Designated Initializer,這個(gè)方法中主要是初始化實(shí)例變量毙死、屬性燎潮,設(shè)置屬性默認(rèn)值,并根據(jù) namespace 設(shè)置完整的緩存目錄路徑扼倘,除此之外确封,還針對(duì) iOS 添加了通知觀察者,用于內(nèi)存緊張時(shí)清空內(nèi)存緩存再菊,以及程序終止運(yùn)行時(shí)和程序退到后臺(tái)時(shí)清掃磁盤緩存爪喘。
- 寫入緩存
寫入緩存的操作主要是由 - storeImage:recalculateFromImage:imageData:forKey:toDisk:
方法處理的,在存儲(chǔ)緩存數(shù)據(jù)時(shí)纠拔,先計(jì)算圖片像素大小秉剑,并存儲(chǔ)到內(nèi)存緩存中去,然后如果需要存到磁盤(沙盒)中稠诲,就開啟異步線程將圖片的二進(jìn)制數(shù)據(jù)存儲(chǔ)到磁盤(沙盒)中侦鹏。
如果需要在存儲(chǔ)之前將傳進(jìn)來(lái)的 image
轉(zhuǎn)成 NSData
,而不是直接使用傳入的 imageData
臀叙,那么就要針對(duì) iOS 系統(tǒng)下略水,按不同的圖片格式來(lái)轉(zhuǎn)成對(duì)應(yīng)的 NSData
對(duì)象。那么圖片格式是怎么判斷的呢劝萤?這里是根據(jù)是否有 alpha 通道以及圖片數(shù)據(jù)的前 8 位字節(jié)來(lái)判斷是不是 PNG 圖片聚请,不是 PNG 的話就按照 JPG 來(lái)處理。
將圖片數(shù)據(jù)存儲(chǔ)到磁盤(沙盒)時(shí)稳其,需要提供一個(gè)包含文件名的路徑驶赏,這個(gè)文件名是一個(gè)對(duì) key
進(jìn)行 MD5 處理后生成的字符串。
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
# 1. 添加內(nèi)存緩存
# 1.1 計(jì)算圖片像素大小
# 1.2 將 image 存入 memCache 中
# 2. 如果需要存儲(chǔ)到沙盒的話既鞠,就異步執(zhí)行磁盤緩存操作
# 2.1 如果需要 recalculate (重新轉(zhuǎn) data)或者傳進(jìn)來(lái)的 imageData 為空的話煤傍,就再轉(zhuǎn)一次 data,因?yàn)榇鏋槲募谋仨毷嵌M(jìn)制數(shù)據(jù)
# 2.1.1 如果 imageData 為 nil嘱蛋,就根據(jù) image 是否有 alpha 通道來(lái)判斷圖片是否是 PNG 格式的
# 2.1.2 如果 imageData 不為 nil蚯姆,就根據(jù) imageData 的前 8 位字節(jié)來(lái)判斷是不是 PNG 格式的,因?yàn)?PNG 圖片有一個(gè)唯一簽名洒敏,前 8 位字節(jié)是(十進(jìn)制): 137 80 78 71 13 10 26 10
# 2.1.3 根據(jù)圖片格式將 UIImage 轉(zhuǎn)為對(duì)應(yīng)的二進(jìn)制數(shù)據(jù) NSData
# 2.2 借助 NSFileManager 將圖片二進(jìn)制數(shù)據(jù)存儲(chǔ)到沙盒龄恋,存儲(chǔ)的文件名是對(duì) key 進(jìn)行 MD5 處理后生成的字符串
}
3.讀取緩存
SDWebImage
在給 UIImageView
加載圖片時(shí)首先需要查詢緩存,查詢緩存的操作主要是 -queryDiskCacheForKey:done:
方法來(lái)實(shí)現(xiàn)的凶伙,該方法首先會(huì)調(diào)用 -imageFromMemoryCacheForKey
方法來(lái)查詢內(nèi)存緩存郭毕,也就是從 memCache
中去找,如果找到了對(duì)應(yīng)的圖片(一個(gè) UIImage
對(duì)象)函荣,就直接回調(diào) doneBlock
显押,并直接返回扳肛。 如果內(nèi)存緩存中沒(méi)有找到對(duì)應(yīng)的圖片,就開啟異步隊(duì)列乘碑,調(diào)用 -diskImageForKey
讀取磁盤緩存挖息,讀取成功之后,再保存到內(nèi)存緩存兽肤,最后再回到主隊(duì)列套腹,回調(diào) doneBlock
。
其中讀取磁盤緩存并不是一步就完成了的资铡,讀取磁盤緩存時(shí)电禀,會(huì)先從沙盒中去找,如果沙盒中沒(méi)有害驹,再?gòu)?customPaths
(也就是 bundle)中去找,找到之后蛤育,再對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換宛官,后面的圖片處理步驟跟圖片下載成功后的圖片處理步驟一樣——先將 data 轉(zhuǎn)成 image,再進(jìn)行根據(jù)文件名中的 @2x瓦糕、@3x 進(jìn)行縮放處理底洗,如果需要解壓縮,最后再解壓縮一下咕娄。
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
# 1.先檢查內(nèi)存緩存亥揖,如果找到了就回調(diào) doneBlock,并直接返回
# 2.開啟異步隊(duì)列圣勒,讀取硬盤緩存
# 2.1 讀取磁盤緩存
# 2.2 如果有磁盤緩存费变,就保存到內(nèi)存緩存
# 2.3 回到主隊(duì)列,回調(diào) doneBlock
}
4.清掃磁盤緩存
每新加載一張圖片圣贸,就會(huì)新增一份緩存挚歧,時(shí)間一長(zhǎng),磁盤上的緩存只會(huì)越來(lái)越多吁峻,所以我們需要定期清除部分緩存滑负。值得注意的是,清掃磁盤緩存(clean)和清空磁盤緩存(clear)是兩個(gè)不同的概念用含,清空是刪除整個(gè)緩存目錄矮慕,清掃只是刪除部分緩存文件。
清掃磁盤緩存有兩個(gè)指標(biāo):一是緩存有效期啄骇,二是緩存體積最大限制痴鳄。SDImageCache
中的緩存有效期是通過(guò) maxCacheAge
屬性來(lái)設(shè)置的,默認(rèn)值是 1 周缸夹,緩存體積最大限制是通過(guò) maxCacheSize
來(lái)設(shè)置的夏跷,默認(rèn)值為 0哼转。
SDImageCache
在初始化時(shí)添加了通知觀察者,所以在應(yīng)用即將終止時(shí)和退到后臺(tái)時(shí)槽华,都會(huì)調(diào)用 -cleanDiskWithCompletionBlock:
方法來(lái)異步清掃緩存壹蔓,清掃磁盤緩存的邏輯是,先遍歷所有緩存文件猫态,并根據(jù)文件的修改時(shí)間來(lái)刪除過(guò)期的文件佣蓉,同時(shí)記錄剩下的文件的屬性和總體積大小,如果設(shè)置了 maxCacheAge
屬性的話亲雪,接下來(lái)就把剩下的文件按修改時(shí)間從小到大排序(最早的排最前面)勇凭,最后再遍歷這個(gè)文件數(shù)組,一個(gè)一個(gè)刪义辕,直到總體積小于 desiredCacheSize 為止虾标,也就是 maxCacheSize 的一半。
知識(shí)點(diǎn)
-
NSCache
是什么灌砖?參考:
NSCache Class Refernce
Effective Objective-C 2.0(Item 50: UseNSCache
Instead ofNSDictionary
for Caches)
Foundation: NSCache
NSCache 源碼(Swift)分析
YYCache 設(shè)計(jì)思路 文件操作和
NSDirectoryEnumerator
如何判斷一個(gè)圖片的格式是 PNG 還是 JPG璧函?
3. 圖片加載管理器——SDWebImageManager
真正加載圖片時(shí),我們需要將下載和緩存兩個(gè)功能結(jié)合起來(lái)基显,這樣才算是一個(gè)完整的圖片加載器蘸吓,SDWebImageManager
就是專門干這個(gè)的。
幾個(gè)問(wèn)題
- 讀取磁盤緩存操作和下載操作都是異步的撩幽,如何管理這兩個(gè)操作(operation)库继?
- 對(duì)于下載失敗過(guò)的 URL,如何處理重試窜醉?
枚舉
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3,
SDWebImageRefreshCached = 1 << 4,
SDWebImageContinueInBackground = 1 << 5,
SDWebImageHandleCookies = 1 << 6,
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11
};
.h 文件中的屬性:
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly) SDImageCache *imageCache; // 緩存器
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader; // 下載器
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter; // 用來(lái)自定義緩存 key 的 block
.m 文件中的屬性:
@property (strong, nonatomic, readwrite) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic) NSMutableSet *failedURLs; // 下載失敗過(guò)的 URL
@property (strong, nonatomic) NSMutableArray *runningOperations; // 正在執(zhí)行中的任務(wù)
.h 文件中的方法:
+ (SDWebImageManager *)sharedManager;
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
// Operation
- (void)cancelAll;
- (BOOL)isRunning;
// Check if image exists
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
- (BOOL)diskImageExistsForURL:(NSURL *)url;
- (void)cachedImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (void)diskImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (NSString *)cacheKeyForURL:(NSURL *)url;
.m 文件中的方法:
// LifeCycle
+ (id)sharedManager;
- (id)init;
- (SDImageCache *)createCache;
// Cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;
// Check if image exists
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
- (BOOL)diskImageExistsForURL:(NSURL *)url;
- (void)cachedImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (void)diskImageExistsForURL:(NSURL *)url completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
// Load image
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
// Save image
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
// Operation
- (void)cancelAll;
- (BOOL)isRunning;
具體實(shí)現(xiàn):
SDWebImageManager
的核心任務(wù)是由 -downloadImageWithURL:options:progress:completed:
方法來(lái)實(shí)現(xiàn)的宪萄,這個(gè)方法中先會(huì)從 SDImageCache
中讀取緩存,如果有緩存榨惰,就直接返回緩存雨膨,如果沒(méi)有就通過(guò) SDWebImageDownloader
去下載,下載成功后再保存到緩存中去读串,然后再回調(diào) completedBlock
聊记。其中 progressBlock
的回調(diào)是直接交給了 SDWebImageDownloader
的 progressBlock
來(lái)處理的。
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
# 1. 對(duì) completedBlock 和 url 進(jìn)行檢查
# 2. 創(chuàng)建 SDWebImageCombinedOperation 對(duì)象
# 3. 判斷是否是曾經(jīng)下載失敗過(guò)的 url
# 4. 如果這個(gè) url 曾經(jīng)下載失敗過(guò)恢暖,并且沒(méi)有設(shè)置 SDWebImageRetryFailed排监,就直回調(diào) completedBlock,并且直接返回
# 5. 添加 operation 到 runningOperations 中
# 6. 計(jì)算緩存用的 key杰捂,讀取緩存
# 7. 處理緩存查詢結(jié)果回調(diào)
# 7.1 判斷 operation 是否已經(jīng)被取消了舆床,如果已經(jīng)取消了就直接移除 operation
# 7.2 進(jìn)一步處理
# 7.2.A 如果緩存中沒(méi)有圖片或者圖片每次都需要更新
# 7.2.A.1 如果有緩存圖片,先回調(diào) completedBlock,回傳緩存的圖片
# 7.2.A.2 開始下載圖片挨队,獲得 subOperation
# 7.2.A.2.1.A 操作被取消谷暮,什么都不干
# 7.2.A.2.1.B 下載失敗
# 7.2.A.2.B.1 沒(méi)有被取消的話,回調(diào) completedBlock
# 7.2.A.2.B.2 如果需要盛垦,則將 URL 加入下載失敗的黑名單
# 7.2.A.2.1.C 下載成功
# 7.2.A.2.1.C.1 將 URL 從下載失敗的黑名單中移除
# 7.2.A.2.1.C.2 緩存圖片
# 7.2.A.2.1.C.3 回調(diào) completedBlock
# 7.2.A.2.2 將 operation 從 runningOperations 中移除
# 7.2.1.3 設(shè)置 SDWebImageCombinedOperation 的 cancelBlock——cancel 掉 subOperation湿弦,并移除 operation
# 7.2.B 如果有緩存圖片且不需要每次更新
# 7.2.B.1 回調(diào) completedBlock
# 7.2.B.2 流程結(jié)束,從 runningOperations 中移除 operation
# 7.2.C 如果沒(méi)有緩存圖片而且不允許下載
# 7.2.B.1 回調(diào) completedBlock
# 7.2.B.2 流程結(jié)束腾夯,從 runningOperations 中移除 operation
}
SDWebImageManager
在讀取緩存和下載之前會(huì)創(chuàng)建一個(gè) SDWebImageCombinedOperation
對(duì)象颊埃,這個(gè)對(duì)象是用來(lái)管理緩存讀取操作和下載操作的,SDWebImageCombinedOperation` 對(duì)象有 3 個(gè)屬性:
-
cancelled
:用來(lái)取消當(dāng)前加載任務(wù)的 -
cancelBlock
:用來(lái)移除當(dāng)前加載任務(wù)和取消下載任務(wù)的 -
cacheOperation
:用來(lái)取消讀取緩存操作
知識(shí)點(diǎn)
4. 設(shè)置 UIImageView 的圖片——UIImageView+WebCache
我們平時(shí)最常用的圖片加載蝶俱,是通過(guò)調(diào)用 UIImageView+WebCache
的 -sd_setImageWithURL:...
系列方法來(lái)加載的班利,UIImageView+WebCache
實(shí)際上是將 SDWebImageManager
封裝了一層,內(nèi)部針對(duì) UIImageView
做了一些處理榨呆,使用起來(lái)更方便罗标、更直接、更簡(jiǎn)單积蜻。
UIImageView+WebCache
的主要任務(wù)是以下幾個(gè):
- 占位圖設(shè)置
- 自動(dòng)管理圖片加載任務(wù)
- 圖片成功獲取后闯割,自動(dòng)設(shè)置圖片顯示
幾個(gè)問(wèn)題
- 如何處理
UIImageView
連續(xù)多次加載圖片的情況,比如在UITableView
的...cellForRow...
方法中加載 cell 上的圖片浅侨? - 如何處理 placeholder image 的顯示邏輯纽谒?
.h 文件中的方法:
- (NSURL *)sd_imageURL;
// Load image for UIImageView
- (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 andPlaceholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
// Animation Image
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
// Cancel
- (void)sd_cancelCurrentImageLoad;
- (void)sd_cancelCurrentAnimationImagesLoad;
.m 文件中的方法:
同 .h 文件中的方法
具體實(shí)現(xiàn):
UIImageView+WebCache
的核心邏輯都在 - sd_setImageWithURL:placeholderImage:options:progress:completed:
方法中证膨,為了防止多個(gè)異步加載任務(wù)同時(shí)存在時(shí)如输,可能出現(xiàn)互相沖突和干擾,該方法中首先通過(guò)調(diào)用 -sd_cancelCurrentImageLoad
方法取消這個(gè) UIImageView
當(dāng)前的下載任務(wù)央勒,然后設(shè)置了占位圖不见,如果 url 不為 nil,接著就調(diào)用 SDWebImageManager
的 -downloadImage...
方法開始加載圖片崔步,并將這個(gè)加載任務(wù) operation 保存起來(lái)稳吮,用于后面的 cancel 操作。圖片獲取成功后井濒,再重新設(shè)置 imageView 的 image灶似,并回調(diào) completedBlock。
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
# 1. 取消當(dāng)前正在進(jìn)行的加載任務(wù)
# 2. 通過(guò) Associated Object 將 url 作為成員變量存起來(lái)
# 3. 設(shè)置占位圖
# 4. 根據(jù) url 是否為 nil 做處理
A. 如果 url 不為 nil
A.1 調(diào)用 SDWebImageManager 的 -downloadImage... 方法開始加載圖片瑞你,并獲得一個(gè) operation
A.1.1 設(shè)置 image
A.1.1.A 圖片下載成功酪惭,設(shè)置 image
A.1.1.B 圖片下載失敗,設(shè)置 placeholder
A.1.1.C 如果不需要自動(dòng)設(shè)置 image者甲,直接 return
A.1.2 回調(diào) completedBlock
A.2 借助 UIView+WebCacheOperation 將獲得的 operation 保存到成員變量中去
B. URL 為空時(shí)春感,直接回調(diào) completedBlock,返回錯(cuò)誤信息
}
值得注意的是,為了防止多個(gè)異步加載任務(wù)同時(shí)存在時(shí)鲫懒,可能出現(xiàn)互相沖突和干擾嫩实,每個(gè) UIImageView
的圖片加載任務(wù)都會(huì)保存成一個(gè) Associated Object,方便需要時(shí)取消任務(wù)窥岩。這個(gè) Associated Object 的操作是在 UIView+WebCacheOperation
中實(shí)現(xiàn)的甲献,因?yàn)槌?UIImageView
用到圖片加載功能之外,還有 UIButton
等其他類也用到了加載遠(yuǎn)程圖片的功能谦秧,所以需要進(jìn)行同樣的處理竟纳,這樣設(shè)計(jì)實(shí)現(xiàn)了代碼的復(fù)用。
知識(shí)點(diǎn)
- UI 操作為什么必須在主線程執(zhí)行疚鲤?
-
-setNeedsLayout
方法
四锥累、知識(shí)點(diǎn)概覽
1.TARGET_OS_IPHONE
宏和 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
宏的使用
這兩個(gè)宏都是用于編譯時(shí)進(jìn)行 SDK 版本適配的宏,主要用于模擬器上的調(diào)試集歇,而針對(duì)真機(jī)上的 iOS 版本適配就需要采用運(yùn)行時(shí)的判斷方式了桶略,比如使用 respondsToSelector: 方法來(lái)判斷當(dāng)前運(yùn)行環(huán)境是否支持該方法的調(diào)用。
參考:http://stackoverflow.com/questions/3269344/what-is-difference-between-these-2-macros/3269562#3269562
http://stackoverflow.com/questions/7542480/what-are-the-common-use-cases-for-iphone-os-version-max-allowed
2.typeof
和 __typeof
诲宇,__typeof__
的區(qū)別
參考:http://stackoverflow.com/questions/14877415/difference-between-typeof-typeof-and-typeof-objective-c
3.使用 -[UIApplication beginBackgroundTaskWithExpirationHandler:]
方法使 app 退到后臺(tái)時(shí)還能繼續(xù)執(zhí)行任務(wù), 不再執(zhí)行后臺(tái)任務(wù)時(shí)际歼,需要調(diào)用 -[UIApplication endBackgroundTask:]
方法標(biāo)記后臺(tái)任務(wù)結(jié)束。
參考:https://developer.apple.com/reference/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio
objective c - Proper use of beginBackgroundTaskWithExpirationHandler
iOS Tips and Tricks: Working in the Background
Background Modes Tutorial: Getting Started
-
NSFoundationVersionNumber
的使用參考:http://stackoverflow.com/questions/19990900/nsfoundationversionnumber-and-ios-versions
-
SDWebImage
文檔中的兩張 Architecture 圖怎么看姑蓝?什么是 UML 類圖鹅心?
6.SDWebImage
的緩存路徑?
格式:Libray/Cache/<#namespace#>/com.hackemist.SDWebImageCache.<#namespace#>/<#MD5_filename#>
如果是默認(rèn)的 namespace纺荧,那么路徑就是 Library/cache/default/com.hackemist.SDWebImageCache.default/<#MD5_filename#>
旭愧,詳見(jiàn) -storeImage:recalculateFromImage:imageData:forKey:toDisk
方法和 -defaultDiskCachePath
方法
7.文件的緩存有效期及最大緩存空間大小
- 默認(rèn)有效期:```maxCacheAge = 60 * 60 * 24 * 7; // 1 week```
- 默認(rèn)最大緩存空間:```maxCacheSize = <#unlimited#>```
8.MKAnnotationView
是用來(lái)干嘛的?
MKAnnotationView
是屬于 MapKit
框架的一個(gè)類宙暇,繼承自 UIView
输枯,是用來(lái)展示地圖上的 annotation 信息的,它有一個(gè)用來(lái)設(shè)置圖片的屬性 image
占贫。
See API Reference: MKAnnotationView
9.圖片下載完成后桃熄,為什么需要用 SDWebImageDecoder
進(jìn)行解碼?
10.SDWebImage
中圖片緩存的 key 是按照什么規(guī)則取的型奥?
SDImageCache
清除磁盤緩存的過(guò)程瞳收?md5 是什么算法?是用來(lái)干什么的厢汹?除此之外螟深,還有哪些類似的加密算法?
-
SDImageCache
讀取磁盤緩存是不是就是指從沙盒中查找并讀取文件坑匠?
五血崭、收獲與疑問(wèn)
- UIImageView 是如何通過(guò) SDWebImage 加載圖片的?
- SDWebImage 在設(shè)計(jì)上有哪些巧妙之處?
- 假如我自己來(lái)實(shí)現(xiàn)一個(gè)圖片下載工具夹纫,我該怎么寫咽瓷?
- SDWebImage 的進(jìn)化史
- SDWebImage 的性能怎么看?
- SDWebImage 是如何處理 gif 圖的舰讹?
六茅姜、啟發(fā)與實(shí)踐
在閱讀 SDWebImage 源碼的過(guò)程中,受到了不少啟發(fā)月匣,所以在不斷完善生成快照功能這個(gè)需求時(shí)钻洒,做了不少重構(gòu)工作,思路也是越做越清晰锄开。
七素标、延伸閱讀
- 讀源碼的正確姿勢(shì)
- iOS 源代碼分析 --- SDWebImage(Draveness)
- SDWebImage實(shí)現(xiàn)分析(南峰子老驢)
- iOS image caching. Libraries benchmark (SDWebImage vs FastImageCache)(SDWebImage 的主要維護(hù)者:bpoplauschi)
- 使用SDWebImage和YYImage下載高分辨率圖,導(dǎo)致內(nèi)存暴增的解決辦法
- SDWebImage 源碼閱讀筆記
- SDWebImage源碼閱讀系列
Contact Me
- 微博:ShannonChenCHN
- Twitter:ShannonChenCHN
- GitHub:ShannonChenCHN
- Email:ShannonChenCHN@foxmail.com