SDWebImage學習

SDWebImage學習

SDWebImage版本是:'4.2.2'web

SDWebImage是iOS開發中經常使用的圖片加載的庫,能下載並緩存圖片。此次就着重介紹SDWebImage的特點功能:下載與緩存。數組

UIImageView+WebCache:直接使用的類
SDWebImageManager:總的管理類,維護了SDWebImageDownloader和SDImageCache實例,是下載與緩存的橋樑
SDWebImageDownloader:圖片的下載隊列
SDWebImageDownloaderOperation:真正的圖片下載請求
SDImageCache:圖片的緩存緩存

SDWebImage的大致流程:安全

下面介紹一下SDWebImage的下載及緩存思路:服務器

下載

在SDWebImage中,圖片的下載是由SDWebImageDownloader類來完成。網絡

UIImageView +WebCache提供的接口方法中,真正進行下載邏輯的是多線程

// ==============  UIView+ WebCache.m ============== //
- (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;

而這個方法是UIView+WebCache分類中的。因爲SDWebImage框架支持UIButton的下載圖片,因此把下載方法放到父類UIView中最爲合適。併發

SDWebImageManager

維護了SDWebImageDownloader和SDImageCache實例,是下載與緩存的橋樑。
在下載任務開始的時候,SDWebImageManager首選會去SDImageCache查詢是否有緩存,有了就直接返回,沒有就去SDWebImageDownloader進行下載,各司其職。框架

SDWebImageManager有這幾個重要的屬性
@property (strong, nonatomic, readwrite, nonnull) SDImageCache imageCache;//管理緩存
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader //下載器
imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL > failedURLs;//記錄失效url的名單
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation > runningOperations;//記錄當前正在執行的操做異步

SDWebImageManager下載的入口方法:loadImageWithURL:options:progress:completed:

下面是他的實現:

- (id <SDWebImageOperation>)loadImageWithURL2:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    ...
    
    //在SDImageCache裏查詢是否存在緩存的圖片
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
         //沒有緩存圖片
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //沒有圖片,刷新緩存
            if (cachedImage && options & SDWebImageRefreshCached) {
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            
            ...
            
            //經過SDWebImageDownloader開始下載
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    
                    //若是出錯,就添加到下載錯誤的failedURLs數組中
                    if (error.code != NSURLErrorNotConnectedToInternet && ...) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    //進行緩存
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        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];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        //下載成功,結束
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            @synchronized(operation) {
                operation.cancelBlock = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            }
        } else if (cachedImage) {
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            // Image not in cache and download disallowed by delegate
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];
    
    return operation;
}
SDWebImageDownloader

SDWebImageDownloader下載管理器是一個單例類,它主要負責圖片的下載操做的管理,有下面幾個關鍵屬性:

@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic) NSOperationQueue *downloadQueue;      //下載隊列
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;

downloadQueue: 來管理下載隊列的,改隊列在子線程中異步執行
barrierQueue: 是一個串行隊列,在一個單一隊列中順序處理全部下載操做的網絡響應,因爲容許多個圖片同時下載,所以可能會有多個線程同時操做URLCallbacks屬性。爲了保證URLCallbacks操做(添加、刪除)的線程安全性,SDWebImageDownloader將這些操做做爲一個個任務放到barrierQueue隊列中。
URLOperations:是NSMutableDictionary來防止下載未完成時,重複添加到下載隊列中

既然是管理類,那麼下載開始,暫停,取消都是支持的,分別是如下的方法。

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

- (void)cancel:(nullable SDWebImageDownloadToken *)token;

- (void)setSuspended:(BOOL)suspended;

- (void)cancelAllDownloads;

下載:入口方法:downloadImageWithURL:options:progress:completed:,方法內部經過SDWebImageDownloaderOperation來進行真正的下載。下載來時先到URLOperations字典中看有沒有已經存在的下載操做,沒有才進行建立SDWebImageDownloaderOperation添加到URLOperations中,並添加到downloadQueue進行隊列管理。

取消單個下載:根據url取出SDWebImageDownloaderOperation,並執行對應的取消方法:- (BOOL)cancel:(nullable id)token;,而後把操做移出字典URLOperations

取消所有:直接調用隊列downloadQueuecancelAllOperations取消所有的方法。

暫停:設置self.downloadQueue.suspended狀態。

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation類,它繼承自NSOperation,,經過NSURLSession的子類NSURLSessionDataTask下載圖片,這是iOS新的Api,老版的是經過NSURLConnection(iOS7.0之前可用)下載的。若是設置了後臺也進行下載SDWebImageDownloaderContinueInBackground那麼會經過UIBackgroundTaskIdentifier來進行相關設置。

//TODO

緩存

SDWebImage提供了對圖片緩存的支持,這樣下次再去獲取同一張圖片時,能夠直接從本地獲取,而再也不從遠程服務器獲取。而該功能是由SDImageCache類來完成的。

SDImageCache包含了內存緩存和磁盤緩存。:內存緩存和磁盤緩存。內存緩存由AutoPurgeCache(NSCache的子類)管理。磁盤緩存是NSFileManager來管理。

SDImageCache定義了一個串行隊列dispatch_queue_t ioQueue,來異步存儲圖片。

存儲方法:

- (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock;

- (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock;

- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock;

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

存儲圖片時以圖片Url的MD5值做爲key進行存儲,存儲的基礎方法是- (void)storeImage: imageData: forKey: toDisk: completion:實現以下:若是設置是須要緩存在內存,就緩存在內存中,再就是存儲在沙盒中。

查詢圖片

在內存或磁盤中查詢是否有key指定的圖片,則能夠分別使用如下方法:

- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

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

- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;

- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;

- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;

刪除圖片

緩存的刪除主要分紅兩種:單個和所有

經過key來刪除單張圖片
下面該方法是主要的方法,經過fromDisk的值來決定刪除內存緩存仍是刪除全部的緩存(包括內存和沙盒)。經過completion來完成刪除後的事項。

其中clearMemory是清理內存緩存,系統註冊了UIApplicationDidReceiveMemoryWarningNotification通知,在內存警告的時候,會主動清理內存緩存。而方法- (void)clearDiskOnCompletion:則是根據緩存的有效期以及最大的緩存大小進行清理。緩存有效期:根據maxCacheAge設置,默認爲一週;最大緩存空間大小:根據maxCacheSize設置。緩存總大小超過這個值得時候,就會根據文件文件最後修改的時間倒序來進行移除。

#pragma mark - Remove Ops
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;

- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;

#pragma mark - Cache clean Ops
- (void)clearMemory;
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

//TODO

總結

學習SDWebImage能學到:

  1. dispatch_barrier_sync函數:確保在執行完任務後纔會執行後續操做。該方法經常使用於確保類的線程安全性操做
  2. @synchronized:多線程訪問數組,保證同一時間只有一個線程在操做
  3. NSMutableURLRequest:用於建立一個網絡請求對象,咱們能夠根據須要來配置請求報頭等信息。
  4. NSOperation及NSOperationQueue:操做隊列是Objective-C中一種高級的併發處理方法,它是基於GCD來實現的。相對於GCD來講,操做隊列的優勢是能夠取消在任務處理隊列中的任務,另外在管理操做間的依賴關係方面也容易一些。對SDWebImage中咱們就看到了如何使用依賴將下載順序設置成後進先出的順序
  5. NSURLSession:用於網絡請求及響應處理。是蘋果在iOS7.0後推出了一套新的網絡請求接口
  6. 開啓一個後臺任務。
  7. NSCache類:一個相似於集合的容器。它存儲key-value對,這一點相似於NSDictionary類。咱們一般用使用緩存來臨時存儲短期使用但建立昂貴的對象。重用這些對象能夠優化性能,由於它們的值不須要從新計算。另一方面,這些對象對於程序來講不是緊要的,在內存緊張時會被丟棄。
  8. 清理緩存圖片的策略:特別是最大緩存空間大小的設置。若是全部緩存文件的總大小超過這一大小,則會按照文件最後修改時間的逆序,以每次一半的遞歸來移除那些過早的文件,直到緩存的實際大小小於咱們設置的最大使用空間。
相關文章
相關標籤/搜索