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中最爲合適。併發
維護了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下載管理器是一個單例類,它主要負責圖片的下載操做的管理,有下面幾個關鍵屬性:
@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
。
取消所有:直接調用隊列downloadQueue
的cancelAllOperations
取消所有的方法。
暫停:設置self.downloadQueue.suspended
狀態。
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能學到: