現在很多APP在tableView加載圖片的時候會給圖片一個加載的效果這是我做的DEMO看一下效果

首先來介紹一下這個 SDWebImage 這個著名開源框架吧, 這個開源框架的主要作用就是:
Asynchronous image downloader with cache support with an UIImageView category.
一個異步下載圖片并且支持緩存的 UIImageView 分類.
就這么直譯過來相信各位也能理解, 框架中最最常用的方法其實就是這個:
[self.imageView sd_setImageWithURL:[NSURL URLWithString:@"url"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
當然這個框架中還有 UIButton 的分類, 可以給 UIButton 異步加載圖片, 不過這個并沒有 UIImageView 分類中的這個方法常用.
這個框架的設計還是極其的優(yōu)雅和簡潔, 主要的功能就是這么一行代碼, 而其中復雜的實現細節(jié)全部隱藏在這行代碼之后, 正應了那句話:
把簡潔留給別人, 把復雜留給自己.
我們已經看到了這個框架簡潔的接口, 接下來我們看一下 SDWebImage 是用什么樣的方式優(yōu)雅地實現異步加載圖片和緩存的功能呢?
接下來我們就以 UIImageView+WebCache 中的
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder;
這一方法為入口研究一下 SDWebImage 是怎樣工作的. 我們打開上面這段方法的實現代碼 UIImageView+WebCache.m
當然你也可以 git clone git@github.com:rs/SDWebImage.git 到本地來查看.
- (void)sd_setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url
placeholderImage:placeholder
options:0
progress:nil
completed:nil];
}
這段方法唯一的作用就是調用了另一個方法
[self sd_setImageWithURL:placeholderImage:options:progress:completed:]
在這個文件中, 你會看到很多的 sd_setImageWithURL...... 方法, 它們最終都會調用上面這個方法, 只是根據需要傳入不同的參數, 這在很多的開源項目中乃至我們平時寫的項目中都是很常見的. 而這個方法也是 UIImageView+WebCache 中的核心方法.
這里就不再復制出這個方法的全部實現了.
操作的管理
這是這個方法的第一行代碼:
// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #1
[self sd_cancelCurrentImageLoad];
這行看似簡單的代碼最開始是被我忽略的, 我后來才發(fā)現蘊藏在這行代碼之后的思想, 也就是 SDWebImage 管理操作的辦法.
框架中的所有操作實際上都是通過一個 operationDictionary 來管理, 而這個字典實際上是動態(tài)的添加到 UIView 上的一個屬性, 至于為什么添加到 UIView 上, 主要是因為這個 operationDictionary 需要在 UIButton 和 UIImageView 上重用, 所以需要添加到它們的根類上.
這行代碼是要保證沒有當前正在進行的異步下載操作, 不會與即將進行的操作發(fā)生沖突, 它會調用:
// UIImageView+WebCache
// sd_cancelCurrentImageLoad #1
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"]
而這個方法會使當前 UIImageView 中的所有操作都被 cancel. 不會影響之后進行的下載操作.
占位圖的實現
// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #4
if (!(options & SDWebImageDelayPlaceholder)) {
self.image = placeholder;
}
如果傳入的 options 中沒有 SDWebImageDelayPlaceholder(默認情況下 options == 0), 那么就會為 UIImageView 添加一個臨時的 image, 也就是占位圖.
獲取圖片
// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #8
if (url)
接下來會檢測傳入的 url 是否非空, 如果非空那么一個全局的 SDWebImageManager 就會調用以下的方法獲取圖片:
[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]
下載完成后會調用 (SDWebImageCompletionWithFinishedBlock)completedBlock 為 UIImageView.image 賦值, 添加上最終所需要的圖片.
// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #10
dispatch_main_sync_safe(^{
if (!wself) return;
if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
dispatch_main_sync_safe 宏定義
上述代碼中的 dispatch_main_sync_safe 是一個宏定義, 點進去一看發(fā)現宏是這樣定義的
#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}
相信這個宏的名字已經講他的作用解釋的很清楚了: 因為圖像的繪制只能在主線程完成, 所以, dispatch_main_sync_safe 就是為了保證 block 能在主線程中執(zhí)行.
而最后, 在 [SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:] 返回 operation 的同時, 也會向 operationDictionary 中添加一個鍵值對, 來表示操作的正在進行:
// UIImageView+WebCache
// sd_setImageWithURL:placeholderImage:options:progress:completed: #28
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
它將 opertion 存儲到 operationDictionary 中方便以后的 cancel.
到此為止我們已經對 SDWebImage 框架中的這一方法分析完了, 接下來我們將要分析 SDWebImageManager 中的方法
[SDWebImageManager.sharedManager downloadImageWithURL:options:progress:completed:]
SDWebImageManager
在 SDWebImageManager.h 中你可以看到關于 SDWebImageManager 的描述:
The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). You can use this class directly to benefit from web image downloading with caching in another context than a UIView.
這個類就是隱藏在 UIImageView+WebCache 背后, 用于處理異步下載和圖片緩存的類, 當然你也可以直接使用 SDWebImageManager 的上述方法 downloadImageWithURL:options:progress:completed: 來直接下載圖片.
可以看到, 這個類的主要作用就是為 UIImageView+WebCache 和 SDWebImageDownloader, SDImageCache 之間構建一個橋梁, 使它們能夠更好的協(xié)同工作, 我們在這里分析這個核心方法的源代碼, 它是如何協(xié)調異步下載和圖片緩存的.
// SDWebImageManager
// downloadImageWithURL:options:progress:completed: #6
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}SDWebImageCompletionBlock
這塊代碼的功能是確定 url 是否被正確傳入, 如果傳入參數的是 NSString 類型就會被轉換為 NSURL. 如果轉換失敗, 那么 url 會被賦值為空, 這個下載的操作就會出錯.
回歸正題,想要做出這個效果,無非是調用了SDWebImage的兩個方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
在completedBlock中我們完成對圖片View的加載
cell.image.alpha = 0.0;
[UIView transitionWithView:cell.image
duration:1.0
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[cell.image setImage:image];
cell.image.alpha = 1.0;
} completion:NULL];
}
寫到這里我們可以達到了漸隱漸現的效果了,但是發(fā)現了一個問題

我們會發(fā)現圖片無論是上拉還是下滑所有的圖片都會重新都會再次加載那我怎樣讓以前加載過的圖片不讓他加載動畫呢,所以就要用SDWebImage讀取緩存的方法
SDWebImageCache
SDWebImageCache.h 這個類在源代碼中有這樣的注釋:
SDImageCache maintains a memory cache and an optional disk cache.
它維護了一個內存緩存和一個可選的磁盤緩存, 我們先來看一下在上一階段中沒有解讀的兩個方法, 首先是:
- (NSOperation *)queryDiskCacheForKey:(NSString *)key
done:(SDWebImageQueryCompletedBlock)doneBlock;
這個方法的主要功能是異步的查詢圖片緩存. 因為圖片的緩存可能在兩個地方, 而該方法首先會在內存中查找是否有圖片的緩存.
// SDWebImageCache
// queryDiskCacheForKey:done: #9
UIImage *image = [self imageFromMemoryCacheForKey:key];
這個 imageFromMemoryCacheForKey 方法會在 SDWebImageCache 維護的緩存 memCache 中查找是否有對應的數據, 而 memCache 就是一個 NSCache.
如果在內存中并沒有找到圖片的緩存的話, 就需要在磁盤中尋找了, 這個就比較麻煩了..
在這里會調用一個方法 diskImageForKey 這個方法的具體實現我在這里就不介紹了, 涉及到很多底層 Core Foundation 框架的知識, 不過這里文件名字的存儲使用 MD5 處理過后的文件名.
// SDImageCache
// cachedFileNameForKey: #6
CC_MD5(str, (CC_LONG)strlen(str), r);
對于其它的實現細節(jié)也就不多說了...
如果在磁盤中查找到對應的圖片, 我們會將它復制到內存中, 以便下次的使用.
// SDImageCache
// queryDiskCacheForKey:done: #24
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage) {
CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
這些就是 SDImageCache 的核心內容了.所以我就用了判斷當前是否儲存過已經加載過的圖片,就不讓他加載的動畫
- (BOOL)diskImageExistsForURL:(NSURL *)url {
NSString *key = [self cacheKeyForURL:url];
return [self.imageCache diskImageExistsWithKey:key];
}
最后由衷感謝draveness深入解析 iOS 開源項目
https://github.com/Draveness/iOS-Source-Code-Analyze,
最后Demo我會上傳到github,歡迎互相交流和學習https://github.com/Jetsond/ZCDemo