最近幾個項目在使用到圖片的時候都采取異步加載的情況,然而原生的ImageView的相關(guān)方式是同步執(zhí)行的,如果圖片太大或者網(wǎng)絡(luò)條件不佳,那么就會造成主界面卡死。所以我們最后才用的是SDWebImage這個庫。像往常一樣,我們還是來簡單分析一下SD都做了哪些事情。
SD的源碼大致可分為兩部分,功能類和Category,功能類包含如下文件,我們來一個個地分析。

SDImageCache負責(zé)管理圖片的緩存,包括使用NSCache實現(xiàn)內(nèi)存緩存以及使用NSFileManger實現(xiàn)磁盤緩存,對外提供了配置參數(shù)、存儲/刪除/查詢圖片緩存的方法。
SDWebImageCompat負責(zé)對Image進行縮放處理,以兼容2x/3x。這里有一個細節(jié)需要注意:
inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image)
方式用了inline進行了修飾,inline的好處是對于需要頻繁調(diào)用的方法,可以提高運行效率。具體可參見這里。于是我按照博客上的例子試了試,看了一下匯編的代碼,發(fā)現(xiàn)并沒有call與不call的區(qū)別,都是"bl _add",于是又Google了一下,發(fā)現(xiàn)了這個
這里面提到即使使用inline,編譯器也不一定就這么做,最終決定權(quán)在編譯器,代碼里的聲明只是建議。而且現(xiàn)在CPU速度很快,遞歸棧的速度也基本忽略不計了,對效率的影響并不大。
SDWebImageDecoder負責(zé)圖片的解碼。這里有個點,即通過 imageNamed 創(chuàng)建 UIImage 時,系統(tǒng)實際上只是把這個文件名放到 UIImage 里返回,并沒有進行實際的文件讀取和解碼。當(dāng) UIImage 第一次顯示到屏幕上時,其內(nèi)部的解碼方法才會被調(diào)用。這樣帶來的結(jié)果就是對于特別大的圖,在顯示的時候會出現(xiàn)延遲。
SDWebImageDownloader負責(zé)圖片的下載。包括設(shè)置是否解碼、并發(fā)量、使用NSOperationQueue管理請求等。
SDWebImageDownloaderOperation繼承NSOperation,處理業(yè)務(wù)層傳遞來的回調(diào)事件。
SDWebImageManager負責(zé)對業(yè)務(wù)層提供下載圖片、緩存圖片、檢查圖片是否存在等方法。
SDWebImagePrefetcher可以預(yù)下載圖片,方便以后使用,個人理解就是比直接用Manager預(yù)加載更方便一點,因為作者已經(jīng)把相關(guān)的代碼封裝好了。
下面以最常用的[UIImageView sd_setImageWithURL]方法為例,分析一下SD的大致流程:
1)取消之前的請求;
[self sd_cancelCurrentImageLoad];
2)設(shè)置占位圖;
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
3)通過SDWebImageManager處理下載邏輯,并將此次加載圖片的請求保存,以便cancel;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//......
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
4)檢查image是否在內(nèi)存中存在;
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
5)檢查image在磁盤中是否存在;
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
6)通過imageDownloader下載圖片;
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
7)在內(nèi)存和磁盤中保存image;
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
8)處理業(yè)務(wù)層的回調(diào);
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);