1. 入口setImageWithUrl:placeHolderImage:options:會把placeHolderImage顯示,而後SDWebImageManager根據URL開始處理圖片.
2. 進入SDWebImageManager-downloadWithURL:delegate:options:userInfo:交給SDImageCache從緩存查找圖片是否已經下載queryDiskCacheForKey:delegate:userInfo:
3. 先從內存圖片緩存查找是否有圖片,若是內存中已經有圖片緩存,SDImageCacheDelegate回調imageCache:didFineImage:forKey:userInfo:到SDWebImageManager.
4. SDWebImageManagerDelegate回調webImageManager:didFinishWithImage:到UIImageView + WebCache等前端展現圖片.
5. 若是內存緩存中沒有,生成NSInvocationOperation添加到隊列開始從硬盤查找圖片是否已經緩存
6. 根據URLKey在硬盤緩存目錄下嘗試讀取圖片文件.這一步是在NSOperation進行的操做,因此回主線程進行結果回調notifyDelegate.
7. 若是上一操做從硬盤讀取到了圖片,將圖片添加到內存緩存中(若是空閒內存太小 會先清空內存緩存).SDImageCacheDelegate 回調imageCache:didFinishImage:forKey:userInfo:進而回調展現圖片.
8. 若是從硬盤緩存目錄讀取不到圖片,說明全部緩存都不存在該圖片,須要下載圖片,回調imageCache:didNotFindImageForKey:userInfo.
9. 共享或從新生成一個下載器SDWebImageDownLoader開始下載圖片
10. 圖片下載由NSURLConnection來作,實現相關delegate來判斷圖片下載中,下載完成和下載失敗
11. connection:didReceiveData:中利用ImageIO作了按圖片下載進度加載效果
12. connectionDidFinishLoading:數據下載完成後交給SDWebImageDecoder作圖片解碼處理
13. 圖片解碼處理在一個NSOperationQueue完成,不會拖慢主線程UI.若是有須要對下載的圖片進行二次處理,最好也在這裏完成,效率會好不少.
14. 在主線程notifyDelegateOnMainThreadWithInfo:宣告解碼完成imageDecoder:didFinishDecodingImage:userInfo:回調給SDWebImageDownloader
15. imageDownLoader:didFinishWithImage:回調給SDWebImageManager告知圖片下載完成
16. 通知全部的downloadDelegates下載完成,回調給須要的地方展現圖片
17. 將圖片保存到SDImageCache中內存緩存和硬盤緩存同時保存,寫文件到硬盤也在以單獨NSInvocationOperation完成,避免拖慢主線程
18. SDImageCache在初始化的時候會註冊一些消息通知,在內存警告或退到後臺的時候清理內存圖片緩存,應用結束的時候清理過時圖片
19. SDWI也提供UIButton + WebCache和MKAnnptation + WebCache方便使用
20. SDWebImagePrefetcher 能夠預先下載圖片,方便後續使用
複製代碼
再用一張圖說明:前端
例如,SD爲UIImageView提供的UIImageView+WebCache.m
分類,有這些API:web
- (void)sd_setImageWithURL:(NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
複製代碼
其實,以上這些API都直接或間接利用到了SDWebImageOptions這個參數,那麼你記得這個參數有哪些類型?各有什麼做用?瀏覽器
經過查看SDWebImageManager.h
源代碼,可知以下:緩存
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
// 默認狀況下,當URL下載失敗時,URL會被列入黑名單,致使庫不會再去重試,該標記用於禁用黑名單
SDWebImageRetryFailed = 1 << 0,
// 默認狀況下,圖片下載開始於UI交互,該標記禁用這一特性,這樣下載延遲到UIScrollView減速時
SDWebImageLowPriority = 1 << 1,
// 該標記禁用磁盤緩存
SDWebImageCacheMemoryOnly = 1 << 2,
// 該標記啓用漸進式下載,圖片在下載過程當中是漸漸顯示的,如同瀏覽器一下。
// 默認狀況下,圖像在下載完成後一次性顯示
SDWebImageProgressiveDownload = 1 << 3,
// 即便圖片緩存了,也指望HTTP響應cache control,並在須要的狀況下從遠程刷新圖片。
// 磁盤緩存將被NSURLCache處理而不是SDWebImage,由於SDWebImage會致使輕微的性能下載。
// 該標記幫助處理在相同請求URL後面改變的圖片。若是緩存圖片被刷新,則完成block會使用緩存圖片調用一次
// 而後再用最終圖片調用一次
SDWebImageRefreshCached = 1 << 4,
// 在iOS 4+系統中,當程序進入後臺後繼續下載圖片。這將要求系統給予額外的時間讓請求完成
// 若是後臺任務超時,則操做被取消
SDWebImageContinueInBackground = 1 << 5,
// 經過設置NSMutableURLRequest.HTTPShouldHandleCookies = YES;來處理存儲在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
// 容許不受信任的SSL認證
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
// 默認狀況下,圖片下載按入隊的順序來執行。該標記將其移到隊列的前面,
// 以便圖片能當即下載而不是等到當前隊列被加載
SDWebImageHighPriority = 1 << 8,
// 默認狀況下,佔位圖片在加載圖片的同時被加載。該標記延遲佔位圖片的加載直到圖片已以被加載完成
SDWebImageDelayPlaceholder = 1 << 9,
// 一般咱們不調用動畫圖片的transformDownloadedImage代理方法,由於大多數轉換代碼能夠管理它。
// 使用這個票房則不任何狀況下都進行轉換。
SDWebImageTransformAnimatedImage = 1 << 10,
};
複製代碼
例如,設置圖片的兩個例子bash
[self.image2 sd_setImageWithURL:imagePath2 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"這裏能夠在圖片加載完成以後作些事情");
}];
複製代碼
//使用默認圖片,並且用block 在完成後作一些事情
[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"圖片加載完成後作的事情");
}];
複製代碼
還有獲取下載進度的例子cookie
//使用默認圖片,並且用block 在完成後作一些事情
[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"圖片加載完成後作的事情");
}];
複製代碼
上面的例子,都有個SDImageCacheTyp
的參數,你記得這個參數有哪些?網絡
//定義Cache類型
typedef NS_ENUM(NSInteger, SDImageCacheType) {
//不使用cache得到圖片,依然會從web下載圖片
SDImageCacheTypeNone,
//圖片從disk得到
SDImageCacheTypeDisk,
//圖片從Memory中得到
SDImageCacheTypeMemory
};
複製代碼
//這個變量默認值爲YES,顯示比較高質量的圖片,可是會浪費比較多的內存,能夠經過設置NO來緩解內存
@property (assign, nonatomic) BOOL shouldDecompressImages;
//總共的內存容許圖片的消耗值
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//圖片存活於內存的時間初始化的時候默認爲一週
@property (assign, nonatomic) NSInteger maxCacheAge;
//每次存儲圖片大小的限制
@property (assign, nonatomic) NSUInteger maxCacheSize;
複製代碼
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager.imageCache setMaxMemoryCost:1000000];//設置總緩存大小,默認爲0沒有限制
[manager.imageCache setMaxCacheSize:640000];//設置單個圖片限制大小
[manager.imageDownloader setMaxConcurrentDownloads:1];//設置同時下載線程數,這是下載器的內容,下面將會介紹
[manager downloadImageWithURL:[NSURL URLWithString:@"http://p9.qhimg.com/t01eb74a44c2eb43193.jpg"]
options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%lu", receivedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
self.imageView1.image = image;
}];
[manager downloadImageWithURL:[NSURL URLWithString:@"http://img.article.pchome.net/00/28/33/87/pic_lib/wm/kuanpin12.jpg"]
options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"%lu", receivedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
self.imageView2.image = image;
}];
NSUInteger size = [manager.imageCache getSize];
NSUInteger count = [manager.imageCache getDiskCount];
NSLog(@"size = %lu", size); // 644621(兩張測試圖片)
NSLog(@"count = %lu", count); // 2
[manager.imageCache clearDisk];
size = [manager.imageCache getSize];
count = [manager.imageCache getDiskCount];
NSLog(@"sizeClean = %lu", size); // 0
NSLog(@"countClean = %lu", count); // 0 這裏使用的是clear
複製代碼
關於緩存位置app
關於圖片下載操做異步
SDWebImage的大部分工做是由緩存對象SDImageCache和異步下載器管理對象SDWebImageManager來完成的。oop
SDWebImage的圖片下載是由SDWebImageDownloader這個類來實現的,它是一個異步下載管理器,下載過程當中增長了對圖片加載作了優化的處理。而真正實現圖片下載的是自定義的一個Operation操做,將該操做加入到下載管理器的操做隊列downloadQueue中,Operation操做依賴系統提供的NSURLConnection類實現圖片的下載。
// AppDelegate.m 文件中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 監控網絡狀態
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
}
複製代碼
而後,在須要的地方獲取監控管理
// 如下代碼在須要監聽網絡狀態的方法中使用
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下載原圖
} else { // 其餘,下載小圖
}
複製代碼
並不能簡單的這樣:WIFI就下載高清圖,蜂窩網絡就下載縮略圖。要考慮和利用緩存的因素。
一個典型例子
- setItem:(CustomItem *)item
{
_item = item;
// 佔位圖片
UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];
// 從內存\沙盒緩存中得到原圖,
UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage];
if (originalImage) { // 若是內存\沙盒緩存有原圖,那麼就直接顯示原圖(無論如今是什麼網絡狀態)
self.imageView.image = originalImage;
} else { // 內存\沙盒緩存沒有原圖
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下載原圖
[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
} else if (mgr.isReachableViaWWAN) { // 在使用手機自帶網絡
// 用戶的配置項假設利用NSUserDefaults存儲到了沙盒中
// [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"];
// [[NSUserDefaults standardUserDefaults] synchronize];
#warning 從沙盒中讀取用戶的配置項:在3G\4G環境是否仍然下載原圖
BOOL alwaysDownloadOriginalImage = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysDownloadOriginalImage"];
if (alwaysDownloadOriginalImage) { // 下載原圖
[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
} else { // 下載小圖
[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];
}
} else { // 沒有網絡
UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage];
if (thumbnailImage) { // 內存\沙盒緩存中有小圖
self.imageView.image = thumbnailImage;
} else { // 處理離線狀態,並且有沒有緩存時的狀況
self.imageView.image = placeholder;
}
}
}
}
複製代碼
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
複製代碼
[self sd_cancelCurrentImageLoad];
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
複製代碼
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
// Cancel in progress downloader from queue
NSMutableDictionary *operationDictionary = [self operationDictionary];
id operations = [operationDictionary objectForKey:key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
複製代碼
setSuspend
嗎?(至少2014年7月的版本)老版本的基於 NSURLConnection 的 SDWebImage 是經過這樣的機制:NSURLConnection工做在主線程,雖然NSURLConnection工做在子線程,但由於UI相關的操做和回調中的setImage
都在同一個主線程,滑動屏幕會致使主線程的runloop切換mode爲UITrackingRunLoopMode
,此時原來kCFRunLoopDefaultMode
上的source (這裏是NSURLConnectionsetImage
) 都被暫停,runloop執行不到回調。
它的本意是不讓網絡相關的操做阻塞到主線程,改正:網絡相關的操做在子線程,主線程runloop的mode切換並不會影響子線程,可是它這樣設計的確有這樣的效果:屏幕滑動時,暫停數據下載的任務,改正:滑動屏幕並不會暫停數據下載,暫停的是同一個主線程的setImage
。
在老版本中,在SDWebImageDownloaderOperation.m文件中有這樣一段話:
然而,新版本的 SDWebImage 是基於 NSURLSession 的,這個NSURLSession不一樣於NSURLConnection的最大區別是否是基於主線程 子線程 的runloop控制的,而是經過NSOperation新開子線程,因此贊成主線程的runloop切換mode並不會影響子線程的操做。因此,新版本的SDWebImage是沒有這個「滑動即暫停」的效果的。改正:一樣,滑動屏幕並不會暫停數據下載,暫停的是同一個主線程的setImage
。
若是,實在有須要,有兩種辦法,能夠本身改寫setImage的方法,在裏面設置工做的mode,同老版的SDWebImage同樣改正:一種是改變setImage的線程或者mode。還有一種辦法,能夠監聽ScrollView的拉拽狀態,當ScrollView的代理方法監聽到被拉拽,就suspend操做。