iOS-SDWebImage史上最詳細(xì)介紹

在iOS的圖片加載框架中尚揣,SDWebImage使用頻率非常高。它支持從網(wǎng)絡(luò)中下載且緩存圖片守屉,并設(shè)置圖片到對(duì)應(yīng)的UIImageView控件或者UIButton控件惑艇。在項(xiàng)目中使用SDWebImage來(lái)管理圖片加載相關(guān)操作可以極大地提高開發(fā)效率,讓我們更加專注于業(yè)務(wù)邏輯實(shí)現(xiàn)。本文講解的版本為4.4.2版本滨巴。

文章目錄結(jié)構(gòu)

一 SDWebImage概論
二 SDWebImage組織架構(gòu)
三 各個(gè)類詳解

一 SDWebImage 概論

SDWebImage是個(gè)支持異步下載與緩存的UIImageView擴(kuò)展思灌。項(xiàng)目主要提供了一下功能:

1.提供了一個(gè)UIImageView的category用來(lái)加載網(wǎng)絡(luò)圖片并且對(duì)網(wǎng)絡(luò)圖片的緩存進(jìn)行管理
2.采用異步方式來(lái)下載網(wǎng)絡(luò)圖片
3.采用異步方式,使用內(nèi)存+磁盤來(lái)緩存網(wǎng)絡(luò)圖片恭取,擁有自動(dòng)的緩存過(guò)期處理機(jī)制泰偿。
4.支持GIF動(dòng)畫
5.支持WebP格式
6.同一個(gè)URL的網(wǎng)絡(luò)圖片不會(huì)被重復(fù)下載
7.失效,虛假的URL不會(huì)被無(wú)限重試
8.耗時(shí)操作都在子線程蜈垮,確保不會(huì)阻塞主線程
9.使用GCD和ARC
10.支持Arm64
11.支持后臺(tái)圖片解壓縮處理
12.項(xiàng)目支持的圖片格式包括 PNG,JPEG,GIF,Webp等

二 SDWebImage組織架構(gòu)
SDWebImage組織架構(gòu).png

關(guān)鍵類講解

SDWebImageDownloader:負(fù)責(zé)維持圖片的下載隊(duì)列耗跛;
SDWebImageDownloaderOperation:負(fù)責(zé)真正的圖片下載請(qǐng)求;
SDImageCache:負(fù)責(zé)圖片的緩存攒发;
SDWebImageManager:是總的管理類调塌,維護(hù)了一個(gè)SDWebImageDownloader實(shí)例和一個(gè)SDImageCache實(shí)例,是下載與緩存的橋梁;
SDWebImageDecoder:負(fù)責(zé)圖片的解壓縮惠猿;
SDWebImagePrefetcher:負(fù)責(zé)圖片的預(yù)雀崂;
UIImageView+WebCache:和其他的擴(kuò)展都是與用戶直接打交道的偶妖。

其中姜凄,最重要的三個(gè)類就是SDWebImageDownloaderSDImageCache趾访、SDWebImageManager态秧。接下來(lái)我們就分別詳細(xì)地研究一下這些類各自具體做了哪些事,又是怎么做的扼鞋。

為了便于大家從宏觀上有個(gè)把握申鱼,這里先給出項(xiàng)目的框架結(jié)構(gòu):

SDWebImage組織架構(gòu).png
  • UIImageView+WebCache和UIButton+WebCache直接為表層的 UIKit框架提供接口
  • SDWebImageManger負(fù)責(zé)處理和協(xié)調(diào)SDWebImageDownloader和SDWebImageCache, 并與 UIKit層進(jìn)行交互。
  • SDWebImageDownloaderOperation真正執(zhí)行下載請(qǐng)求云头;最底層的兩個(gè)類為高層抽象提供支持润讥。
三 各個(gè)類詳解

我們按照上圖中從上到下執(zhí)行的流程來(lái)研究各個(gè)類

3.1 UIImageView+WebCache

這里只用UIImageView+WebCache來(lái)舉個(gè)例子,其他的擴(kuò)展類似盘寡。

使用場(chǎng)景:已知圖片的url地址,下載圖片并設(shè)置到UIImageView上撮慨。

UIImageView+WebCache提供了一系列的接口:

- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithPreviousCachedImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock;

這些接口最終都會(huì)調(diào)用

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

新版本還給UIView增加了分類竿痰,即UIView+WebCache,最終上述方法會(huì)走到下面的方法去具體操作砌溺,比如下載圖片等影涉。

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary<NSString *, id> *)context;

接下來(lái)對(duì)該方法進(jìn)行解析

  • 第一步:取消當(dāng)前正在進(jìn)行的異步下載,確保每個(gè) UIImageView 對(duì)象中永遠(yuǎn)只存在一個(gè) operation规伐,當(dāng)前只允許一個(gè)圖片網(wǎng)絡(luò)請(qǐng)求蟹倾,該 operation 負(fù)責(zé)從緩存中獲取 image 或者是重新下載 image。具體執(zhí)行代碼是:
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 取消先前下載的任務(wù)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
... // 下載圖片操作
// 將生成的加載操作賦值給UIView的自定義屬性
[self sd_setImageLoadOperation:operation forKey:validOperationKey];

上述方法定義在UIView+WebCacheOperation類中

- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
    if (key) {
        // 如果之前已經(jīng)有過(guò)該圖片的下載操作,則取消之前的圖片下載操作
        [self sd_cancelImageLoadOperationWithKey:key];
        if (operation) {
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            @synchronized (self) {
                [operationDictionary setObject:operation forKey:key];
            }
        }
    }
}

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    if (key) {
        // Cancel in progress downloader from queue
        SDOperationsDictionary *operationDictionary = [self sd_operationDictionary]; // 獲取添加在UIView的自定義屬性
        id<SDWebImageOperation> operation;
        
        @synchronized (self) {
            operation = [operationDictionary objectForKey:key];
        }
        if (operation) {
            // 實(shí)現(xiàn)了SDWebImageOperation的協(xié)議
            if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                [operation cancel];
            }
            @synchronized (self) {
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
}

實(shí)際上,所有的操作都是由一個(gè)實(shí)際上鲜棠,所有的操作都是由一個(gè)operationDictionary字典維護(hù)的,執(zhí)行新的操作之前肌厨,cancel所有的operation。

  • 第二步:占位圖策略
    作為圖片下載完成之前的替代圖片豁陆。dispatch_main_async_safe是一個(gè)宏柑爸,保證在主線程安全執(zhí)行。
if (!(options & SDWebImageDelayPlaceholder)) {
    dispatch_main_async_safe(^{
        // 設(shè)置占位圖
        [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
    });
}
  • 第三步:判斷url是否合法
    如果url合法盒音,則進(jìn)行圖片下載操作表鳍,否則直接block回調(diào)失敗
if (url) {
    // 下載圖片操作
} else {
    dispatch_main_async_safe(^{
#if SD_UIKIT
        [self sd_removeActivityIndicator];
#endif
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            completedBlock(nil, error, SDImageCacheTypeNone, url);
        }
    });
}
  • 第四步 下載圖片操作
    下載圖片的操作是由SDWebImageManager完成的,它是一個(gè)單例
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock;

下載完成之后刷新UIImageView的圖片祥诽。

// 根據(jù)枚舉類型譬圣,判斷是否需要設(shè)置圖片
shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                          (!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
    if (!sself) { return; }
    if (!shouldNotSetImage) {
        [sself sd_setNeedsLayout];  // 設(shè)置圖片
    }
    if (completedBlock && shouldCallCompletedBlock) {
        completedBlock(image, error, cacheType, url);
    }
};

if (shouldNotSetImage) {    // 不要自動(dòng)設(shè)置圖片,則調(diào)用block傳入image對(duì)象
    dispatch_main_async_safe(callCompletedBlockClojure);
    return;
}

// 設(shè)置圖片操作
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
#endif
    callCompletedBlockClojure();
});

最后,把返回的id operation添加到operationDictionary中雄坪,方便后續(xù)的cancel厘熟。

// 將生成的加載操作賦值給UIView的自定義屬性
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
3.2 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背后,用于處理異步下載和圖片緩存的類诸衔,當(dāng)然你也可以直接使用 SDWebImageManager 的方法 來(lái)直接下載圖片盯漂。

- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;

SDWebImageManager.h首先定義了一些枚舉類型的SDWebImageOptions

然后笨农,聲明了四個(gè)block:

//操作完成的回調(diào)就缆,被上層的擴(kuò)展調(diào)用。
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);

//被SDWebImageManager調(diào)用谒亦。如果使用了SDWebImageProgressiveDownload標(biāo)記竭宰,這個(gè)block可能會(huì)被重復(fù)調(diào)用,直到圖片完全下載結(jié)束份招,finished=true,再最后調(diào)用一次這個(gè)block切揭。
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);

//SDWebImageManager每次把URL轉(zhuǎn)換為cache key的時(shí)候調(diào)用,可以刪除一些image URL中的動(dòng)態(tài)部分锁摔。
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);

定義了SDWebImageManagerDelegate協(xié)議:

@protocol SDWebImageManagerDelegate 
 
@optional
// 控制在cache中沒有找到image時(shí) 是否應(yīng)該去下載廓旬。
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;

// 在下載之后,緩存之前轉(zhuǎn)換圖片谐腰。在全局隊(duì)列中操作孕豹,不阻塞主線程
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;

@end

SDWebImageManager是單例使用的,分別維護(hù)了一個(gè)SDImageCache實(shí)例和一個(gè)SDWebImageDownloader實(shí)例十气。 對(duì)象方法分別是:

//初始化SDWebImageManager單例励背,在init方法中已經(jīng)初始化了cache單例和downloader單例。
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
//下載圖片
- (id )downloadImageWithURL:(NSURL *)url
                    options:(SDWebImageOptions)options
                   progress:(SDWebImageDownloaderProgressBlock)progressBlock
                  completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//緩存給定URL的圖片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消當(dāng)前所有的操作
- (void)cancelAll;
//監(jiān)測(cè)當(dāng)前是否有進(jìn)行中的操作
- (BOOL)isRunning;
//監(jiān)測(cè)圖片是否在緩存中砸西, 先在memory cache里面找  再到disk cache里面找
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
//監(jiān)測(cè)圖片是否緩存在disk里
- (BOOL)diskImageExistsForURL:(NSURL *)url;
//監(jiān)測(cè)圖片是否在緩存中,監(jiān)測(cè)結(jié)束后調(diào)用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//監(jiān)測(cè)圖片是否緩存在disk里,監(jiān)測(cè)結(jié)束后調(diào)用completionBlock
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回給定URL的cache key
- (NSString *)cacheKeyForURL:(NSURL *)url;

我們主要研究

- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;
  1. 首先叶眉,監(jiān)測(cè)url 的合法性:
if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
    url = nil;
}

第一個(gè)判斷條件是防止很多用戶直接傳遞NSString作為NSURL導(dǎo)致的錯(cuò)誤址儒,第二個(gè)判斷條件防止crash。

2.集合failedURLs保存之前失敗的urls衅疙,如果url為空或者url之前失敗過(guò)且不采用重試策略莲趣,直接調(diào)用completedBlock返回錯(cuò)誤。

BOOL isFailedUrl = NO;
if (url) {  // 判斷url是否是失敗過(guò)的url
    LOCK(self.failedURLsLock);
    isFailedUrl = [self.failedURLs containsObject:url];
    UNLOCK(self.failedURLsLock);
}
// 如果url為空或者url下載失敗并且設(shè)置了不再重試
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
    [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
    return operation;
}

3.保存操作

LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock);

runningOperations是一個(gè)可變數(shù)組炼蛤,保存所有的operation妖爷,主要用來(lái)監(jiān)測(cè)是否有operation在執(zhí)行,即判斷running 狀態(tài)理朋。

4.查找緩存
SDWebImageManager會(huì)首先在memory以及disk的cache中查找是否下載過(guò)相同的照片絮识,即調(diào)用imageCache的下面方法

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;

如果操作取消,則直接返回

__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {  // operation取消,那么將下載任務(wù)從下載隊(duì)列中直接移除
    [self safelyRemoveOperationFromRunning:strongOperation];
    return;
}

如果沒有在緩存中找到圖片嗽上,或者不管是否找到圖片次舌,只要operation有SDWebImageRefreshCached標(biāo)記,那么若SDWebImageManagerDelegate的shouldDownloadImageForURL方法返回true兽愤,即允許下載時(shí)彼念,都使用 imageDownloader 的下載方法

- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

如果下載有錯(cuò)誤,直接調(diào)用completedBlock返回錯(cuò)誤浅萧,并且視情況將url添加到failedURLs里面逐沙;

dispatch_main_sync_safe(^{
    if (strongOperation && !strongOperation.isCancelled) {
        completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
    }
});
 
if (error.code != NSURLErrorNotConnectedToInternet
 && error.code != NSURLErrorCancelled
 && error.code != NSURLErrorTimedOut
 && error.code != NSURLErrorInternationalRoamingOff
 && error.code != NSURLErrorDataNotAllowed
 && error.code != NSURLErrorCannotFindHost
 && error.code != NSURLErrorCannotConnectToHost) {
      @synchronized (self.failedURLs) {
          [self.failedURLs addObject:url];
      }
}

如果下載成功,若支持失敗重試洼畅,將url從failURLs里刪除:

if ((options & SDWebImageRetryFailed)) {
    @synchronized (self.failedURLs) {
         [self.failedURLs removeObject:url];
    }
}

如果delegate實(shí)現(xiàn)了吩案,imageManager:transformDownloadedImage:withURL:方法,圖片在緩存之前帝簇,需要做轉(zhuǎn)換(在全局隊(duì)列中調(diào)用徘郭,不阻塞主線程)。轉(zhuǎn)化成功切下載全部結(jié)束丧肴,圖片存入緩存残揉,調(diào)用completedBlock回調(diào),第一個(gè)參數(shù)是轉(zhuǎn)換后的image芋浮。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
 
    if (transformedImage && finished) {
        BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
        //將圖片緩存起來(lái)
        [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
    }
    dispatch_main_sync_safe(^{
        if (strongOperation && !strongOperation.isCancelled) {
            completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
        }
    });
});

否則抱环,直接存入緩存,調(diào)用completedBlock回調(diào)纸巷,第一個(gè)參數(shù)是下載的原始image江醇。

if (downloadedImage && finished) {
    [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
 
dispatch_main_sync_safe(^{
    if (strongOperation && !strongOperation.isCancelled) {
        completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
    }
});

存入緩存都是調(diào)用imageCache的下面方法

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk

如果沒有在緩存找到圖片,且不允許下載何暇,直接調(diào)用completedBlock,第一個(gè)參數(shù)為nil凛驮。

dispatch_main_sync_safe(^{
    __strong __typeof(weakOperation) strongOperation = weakOperation;
    if (strongOperation && !weakOperation.isCancelled) {//為啥這里用weakOperation TODO
        completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
    }
});

最后都要將這個(gè)operation從runningOperations里刪除裆站。

@synchronized (self.runningOperations) {
    [self.runningOperations removeObject:operation];
 }
3.3 SDWebImageCombinedOperation
@interface SDWebImageCombinedOperation : NSObject 
 
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
 
@end

是一個(gè)遵循SDWebImageOperation協(xié)議的NSObject子類。

@protocol SDWebImageOperation 
 
- (void)cancel;
 
@end

在里面封裝一個(gè)NSOperation,這么做的目的應(yīng)該是為了使代碼更簡(jiǎn)潔宏胯。因?yàn)橄螺d操作需要查詢緩存的operation和實(shí)際下載的operation羽嫡,這個(gè)類的cancel方法可以同時(shí)cancel兩個(gè)operation,同時(shí)還可以維護(hù)一個(gè)狀態(tài)cancelled肩袍。

SDWebImage 使用

1.使用IImageView+WebCache category來(lái)加載UITableView中cell的圖片

[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img1.cache.netease.com/catchpic/5/51/5132C377F99EEEE927697E62C26DDFB1.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

2.使用Blocks,采用這個(gè)方案可以在網(wǎng)絡(luò)圖片加載過(guò)程中得知圖片的下載進(jìn)度和圖片加載成功與否

[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img1.cache.netease.com/catchpic/5/51/5132C377F99EEEE927697E62C26DDFB1.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
    //... completion code here ... 
 }];

3.使用SDWebImageManager,SDWebImageManager為UIImageView+WebCache category的實(shí)現(xiàn)提供接口杭棵。

SDWebImageManager *manager = [SDWebImageManager sharedManager] ;
[manager downloadImageWithURL: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
   }
 }];

4.加載圖片還有使用SDWebImageDownloader和SDImageCache方式
5.key的來(lái)源

// 利用Image的URL生成一個(gè)緩存時(shí)需要的key.
// 這里有兩種情況,第一種是如果檢測(cè)到cacheKeyFilter不為空時(shí),利用cacheKeyFilter來(lái)處理URL生成一個(gè)key.
// 如果為空,那么直接返回URL的string內(nèi)容,當(dāng)做key.
- (NSString *)cacheKeyForURL:(NSURL *)url {
    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    }
    else {
        return [url absoluteString];
    }
}
SDWebImage 流程
656644-7dfe370a86e157e7.png
SDWebImage 接口

SDWebImage是一個(gè)成熟而且比較龐大的框架,但是在使用過(guò)程中并不需要太多的接口,這算是一種代碼封裝程度的體現(xiàn)氛赐。這里就介紹比較常用的幾個(gè)接口魂爪。

1.給UIImageView設(shè)置圖片的接口,SDWebImage有提供多個(gè)給UIImageView設(shè)置圖片的接口艰管,最終所有的接口都會(huì)調(diào)用下圖的這個(gè)接口滓侍,這是大多數(shù)框架的做法。

///所以設(shè)置圖片的方法最終都會(huì)調(diào)用該方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
}

2.獲取SDWebImage的磁盤緩存大小,在項(xiàng)目中有時(shí)候會(huì)需要統(tǒng)計(jì)應(yīng)用的磁盤緩存內(nèi)容大小牲芋,那么獲取圖片的緩存大小就是使用這個(gè)接口來(lái)實(shí)現(xiàn)

[SDImageCache sharedImageCache] getSize];

3.清理內(nèi)存緩存撩笆,清理內(nèi)存中緩存的圖片資源,釋放內(nèi)存資源缸浦。

[[SDImageCache sharedImageCache] clearMemory];

4.有了清理內(nèi)存緩存夕冲,自然也有清理磁盤緩存的接口

[[SDImageCache sharedImageCache] clearDisk];
SDWebImage 解析

<1>入口 setImageWithURL:placeholderImage:options: 會(huì)先把 placeholderImage 顯示,然后 SDWebImageManager 根據(jù) URL 開始處理圖片裂逐。

<2>進(jìn)入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:歹鱼,交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:.

<3>先從內(nèi)存圖片緩存查找是否有圖片,如果內(nèi)存中已經(jīng)有圖片緩存絮姆,SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager醉冤。

<4>SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。

<5>如果內(nèi)存緩存中沒有篙悯,生成 NSInvocationOperation 添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存蚁阳。

<6>根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進(jìn)行的操作鸽照,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:螺捐。

<7>如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過(guò)小矮燎,會(huì)先清空內(nèi)存緩存)定血。SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:。進(jìn)而回調(diào)展示圖片诞外。

<8>如果從硬盤緩存目錄讀取不到圖片澜沟,說(shuō)明所有緩存都不存在該圖片,需要下載圖片峡谊,回調(diào) imageCache:didNotFindImageForKey:userInfo:茫虽。

<9>共享或重新生成一個(gè)下載器 SDWebImageDownloader 開始下載圖片刊苍。

<10>圖片下載由 NSURLConnection 來(lái)做,實(shí)現(xiàn)相關(guān) delegate 來(lái)判斷圖片下載中濒析、下載完成和下載失敗正什。

<11>connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進(jìn)度加載效果。

<12>connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理号杏。

<13>圖片解碼處理在一個(gè) NSOperationQueue 完成婴氮,不會(huì)拖慢主線程 UI。如果有需要對(duì)下載的圖片進(jìn)行二次處理盾致,最好也在這里完成主经,效率會(huì)好很多。

<14>在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成绰上,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader旨怠。

<15>imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成。

<16>通知所有的 downloadDelegates 下載完成蜈块,回調(diào)給需要的地方展示圖片鉴腻。

<17>將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤緩存同時(shí)保存百揭。寫文件到硬盤也在以單獨(dú) NSInvocationOperation 完成爽哎,避免拖慢主線程。

<18>SDImageCache 在初始化的時(shí)候會(huì)注冊(cè)一些消息通知器一,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存课锌,應(yīng)用結(jié)束的時(shí)候清理過(guò)期圖片。

<19>SDWebImage 也提供了 UIButton+WebCache 和MKAnnotationView+WebCache祈秕,方便使用渺贤。

<20>SDWebImagePrefetcher 可以預(yù)先下載圖片,方便后續(xù)使用请毛。

從上面流程可以看出志鞍,當(dāng)你調(diào)用setImageWithURL:方法的時(shí)候,他會(huì)自動(dòng)去給你干這么多事方仿,當(dāng)你需要在某一具體時(shí)刻做事情的時(shí)候固棚,你可以覆蓋這些方法。比如在下載某個(gè)圖片的過(guò)程中要響應(yīng)一個(gè)事件仙蚜,就覆蓋這個(gè)方法:
//覆蓋方法此洲,指哪打哪,這個(gè)方法是下載imagePath2的時(shí)候響應(yīng)
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
     
    [manager downloadImageWithURL:imagePath2 options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
         
        NSLog(@"顯示當(dāng)前進(jìn)度");
         
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
         
        NSLog(@"下載完成");
    }];

簡(jiǎn)書常用格式


  • 如有錯(cuò)誤委粉,歡迎指正呜师,多多點(diǎn)贊,打賞更佳贾节,您的支持是我寫作的動(dòng)力匣掸。

項(xiàng)目連接地址 -

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末趟紊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碰酝,更是在濱河造成了極大的恐慌,老刑警劉巖戴差,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件送爸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡暖释,警方通過(guò)查閱死者的電腦和手機(jī)袭厂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)球匕,“玉大人纹磺,你說(shuō)我怎么就攤上這事×敛埽” “怎么了橄杨?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)照卦。 經(jīng)常有香客問(wèn)我式矫,道長(zhǎng),這世上最難降的妖魔是什么役耕? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任采转,我火速辦了婚禮,結(jié)果婚禮上瞬痘,老公的妹妹穿的比我還像新娘故慈。我一直安慰自己,他們只是感情好框全,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布察绷。 她就那樣靜靜地躺著,像睡著了一般竣况。 火紅的嫁衣襯著肌膚如雪克婶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天丹泉,我揣著相機(jī)與錄音情萤,去河邊找鬼。 笑死摹恨,一個(gè)胖子當(dāng)著我的面吹牛筋岛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晒哄,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼睁宰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肪获!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起柒傻,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤孝赫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后红符,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體青柄,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年预侯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了致开。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萎馅,死狀恐怖双戳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糜芳,我是刑警寧澤飒货,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站耍目,受9級(jí)特大地震影響膏斤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邪驮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一莫辨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毅访,春花似錦沮榜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至守呜,卻和暖如春型酥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背查乒。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工弥喉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玛迄。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓哭当,卻偏偏與公主長(zhǎng)得像刷晋,于是被迫代替她去往敵國(guó)和親额衙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容