源碼解析ios開發(fā)SDWebImage方法
引言
在著手寫第二篇的時候,發(fā)現這個SDWebimage確實吧NSOperation用的太美了。確實可能幫你理解NSOperation和NSOperationQueue,當然還有Block的隊列。還有一個GCD。
各位看官在看的時候可以著重的看看他的operatinQueue的隊列??纯词窃趺刺砑拥疥犃械囊约笆窃趺匆瞥犃小T诤竺娴奈恼戮蜁岬剿窃趺磮?zhí)行的。 還要注重看的就是以前用的NSURLConnection而現在用的NSURLSession下來了
最近在面試,發(fā)現有人問這個組件,所有讀一讀,應付一下面試吧??!
源碼解析
廢話不多說看源碼。
- 1:在組件中提供了很多類似這樣的方法
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
- 2:于是乎所有的方法都會調用下面的這個方法
- (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
- 3:下面具體閱讀代碼 第一行執(zhí)行的代碼
//********1: 所有的設置控件圖片都是經過該方法******* NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); //********2: 取消當前控件正在operations的隊列******* [self sd_cancelImageLoadOperationWithKey:validOperationKey];
解析:
1.在第一行創(chuàng)建了validOperationKey的operationKey,是以當前擴展的類名命名。
2.執(zhí)行sd_cancelImageLoadOperationWithKey方法
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
/**
* 在該對象中,用runtime手動的添加一個字典屬性。
### 說一下這里的operationDictionary
### 該字典的value是下載的操作,然而key是對視圖和操作來做的標識字符串
*/
SDOperationsDictionary *operationDictionary = [self operationDictionary];
/*
* 在生成字典中的都是新的,所有都沒有數據
*/
id operations = operationDictionary[key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
字典操作
在看一下去字典操作[self operationDictionary]
- (SDOperationsDictionary *)operationDictionary {
/**
### 這里有一個疑問?
這樣創(chuàng)建出來的字典每一次都是新的
雖然用了 static char loadOperationKey,但是沒一次創(chuàng)建的地址都是新的,并且該字典還是空的。
創(chuàng)建完成之后都直接返回了。不知道每次創(chuàng)建的都是新的并且還是空的字典,這樣的開銷會很大的???。?!,也希望各位看官給予解答?。???
*/
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
// 創(chuàng)建成功返回該字典,直接把該字典當做屬性返回
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
看到這里我們在返回繼續(xù)看 這個函數的代碼有點長,耐心看完啊。。。
- (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 {
//********1: 所有的設置控件圖片都是經過該方法*******
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
/**2: 取消當前控件正在operations的隊列
* 至于為什么在該控件下載前都要清空該控件對應的所有的下載隊列?
* 可能是,如果要給控件賦值新的圖片的話,之前所有的操作都和當前的操作無關,所有就取消吧
*******/
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/**3:設置占位圖片*/
/**這里的意思就是options參數不是SDWebImageDelayPlaceholder,就執(zhí)行以下操作 */
if (!(options & SDWebImageDelayPlaceholder)) {
/**
* 注意這的里宏定義
* #ifndef dispatch_main_async_safe
* #define dispatch_main_async_safe(block)\
* if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
* block();\
* } else {\
* dispatch_async(dispatch_get_main_queue(), block);\
* }
* #endif
可以看出都把block方法主線程中去執(zhí)行。
應為在系統里,只能有一個線程去執(zhí)行UI的更新,那就是主線程。
如果能在其他線程更新,那這世界就亂了,像了解
更仔細的問題可以關注www.osjoin.com查看是為什么!
*/
dispatch_main_async_safe(^{
/**設置占位圖placeholder*/
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
/**4:菊花提示*/
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
/**5:下載圖片
* 進入下載圖片最重要的函數也是核心的函數了
這個函數關聯的函數較多,先簡單過一下。
*/
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
/**將行的下操作放到uiview的下載隊列中的自定義的字典中去*/
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
看一下調用下載函數前的實例化過程
這個loadImageWithURL函數在SDWebImageManager,并且是用單列調用,
+ (nonnull instancetype)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
/**
###此處有其他重要配置,下一篇文章解讀
###此處有其他重要配置,下一篇文章解讀
###此處有其他重要配置,下一篇文章解讀
*/
SDImageCache *cache = [SDImageCache sharedImageCache];
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
return [self initWithCache:cache downloader:downloader];
}
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
/**這里的failedURLS是NSSet也就沒重復的屬性*/
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
上面都是初始化的操作,然后看下面的函數
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
/**
這里防止用戶輸入的類型錯誤,轉換一下
*/
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
/*! 如果轉換失敗,url為nil 返回 */
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
/**5.1:搞一個新的下載隊列*/
/*! 這里就又__block和__weak的用法區(qū)別
這里簡單說說一下
1:__block,在block函數里可以修改和閱讀
2:__weak可以避免循環(huán)引用,在給他設置新數據的時候,設置方法既不保留新值,也不釋放舊值
*/
/*! 說一下SDWebImageCombinedOperation
他擁有了一個緩存隊列和一個能取消執(zhí)行的block,并且還遵守了SDWebImageOperation一個協議
*/
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
/**5.2判斷URL是否是下載失敗的url*/
BOOL isFailedUrl = NO;
if (url) {
/*! 創(chuàng)建一個互斥鎖防止有其他線程同時修改該對象 */
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
/**
* 5.3url不存在或者下載失敗的url 則不在繼續(xù)下載隊列
* 返回一個completedBlock
*/
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;
}
/**5.4把url添加在下載隊列
把operation加入到self.runningOperations的數組里面,
并創(chuàng)建一個互斥線程鎖來保護這個操作
*/
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
/*! 獲取image的url對應的key */
NSString *key = [self cacheKeyForURL:url];
/**5.5快速查找***緩存*****/
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
/**
* 這里的狀態(tài)改變,在sd_cancelImageLoadOperationWithKey方法中調用代理函數時會改變該狀態(tài)cancel
* 如果該隊列是取消狀態(tài),直接從下載隊列中移除,此處有一個安全鎖
*/
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
/**下載完成之后執(zhí)行圖片轉換處理和緩存操作**/
//條件1:在緩存中沒有找到圖片或者options選項里面包含了SDWebImageRefreshCached(這兩項都需要進行請求網絡圖片的)
//條件2:代理允許下載,SDWebImageManagerDelegate的delegate不能響應imageManager:shouldDownloadImageForURL:方法或者能響應方法且方法返回值為YES.也就是沒有實現這個方法就是允許的,如果實現了的話,返回YES才是允許
if ((!cachedImage || options & SDWebImageRefreshCached) &&
(![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果在緩存中找到了image且options選項包含SDWebImageRefreshCached,先在主線程完成一次回調,使用的是緩存中找的圖片
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
/** 如果在緩存中找到了image但是設置了SDWebImageRefreshCached選項,傳遞緩存的image,同時嘗試重新下載它來讓NSURLCache有機會接收服務器端的更新
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completionBlock) {
completionBlock(image, data, error, cacheType, finished, url);
}
});
*/
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// 如果沒有在緩存中找到image 或者設置了需要請求服務器刷新的選項,則仍需要下載
// download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
// 如果image已經被緩存但是設置了需要請求服務器刷新的選項,強制關閉漸進式選項
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
// 如果image已經被緩存但是設置了需要請求服務器刷新的選項,忽略從NSURLCache讀取的image
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
/**如果在緩存和硬盤上都沒查到url對應的圖片
***則進行圖片下載
*/
/*! 進入下載操作就是2.2中的操作了*/
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url
options:downloaderOptions
progress:progressBlock
completed:^(UIImage *downloadedImage,
NSData *downloadedData,
NSError *error,
BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
/*! 如果為取消狀態(tài),啥也不錯,閑著 */
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// 不用做任何事情,如果是取消狀態(tài)
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
//如果我們調用completedBlock,這個block會和另外一個completedBlock爭奪一個對象,因此這個block被調用后會覆蓋新的數據
} else if (error) {
//進行完成回調
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
//將失敗的url添加到failedURLS的set中去
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
//如果有重試狀態(tài),將url從失敗列表中移除
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//options包含了SDWebImageRefreshCached選項,且緩存中找到了image且沒有下載成功
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (
//圖片下載成功并且 設置了需要變形Image的選項且變形的代理方法已經實現
downloadedImage &&
(!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) &&[self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]
) {
/**
* dispatch_get_global_queue創(chuàng)建的一個//全局隊列異步隊列執(zhí)行
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//調用代理方法完成圖片transform
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
//對已經transform的圖片進行緩存
[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 {
//如果沒有圖片transform的需求并且圖片下載完成且圖片存在就直接緩存
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];
}
}];
/**取消的回調*/
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
//在緩存中找到圖片(代理不允許下載 或者沒有設置SDWebImageRefreshCached選項 滿足至少一項)
} 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;
}
這個函數就進入了SDWebimage緩存的策略了。
先說一下他的這一個策略緩存。
*1:大家都是SDWebiamge都是先從緩存上查找,如果有就直接顯示
*2:如果不存在就在沙盒中查找
- *2.1如果存在,則把沙盒中的圖片添加到imageCache中,取出顯示
- *2.2 如果不存在在顯示占位圖,根據URL在operationCache是否存在下載操作
*2.2.1 如果存在,說明該圖片正在下載。
*2.2.2如果不存在,創(chuàng)建圖片下載操作,放到operationCache中
- * 2.3 下載完成,將當前操作隊列從operationCache中移除。并將下載的圖片的添加在imageCache中。顯示
先慢慢體會一下。。。(停留30秒)
開始進入查找函數
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
/**從緩存中查找圖片開始*/
/*! 檢查key是否為空(URL) */
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 先檢查緩存--內存上的數據
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
/**從內存在檢查到有圖片**/
NSData *diskData = nil;
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
/**如果有直接返回在view上顯示*/
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
/**如果內存上沒有數據,則在硬盤上查找,如果找到了該圖片,就放到緩存上用doneBlock完成回調**/
/*! 這一塊創(chuàng)建了異步隊列
這里的self.ioQueue是這樣定義的
****@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
****這里開始了串行的隊列去處理硬盤上的緩存
*/
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
/**如果是取消的 就直接返回*/
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
/*! 開了手動釋放池 */
@autoreleasepool {
/**從磁盤中讀取圖片*/
/*! 根據url去硬盤上查找數據 */
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
/**如果在硬盤中讀取到圖片,則把沙盒中的圖片放到Cache中*/
//self.memCache是NSCache創(chuàng)建的一個對象
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
/*! 在主線程放回數據 */
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
快速查找緩存的方法回調
看完該函數以后在回到上面的看這個快速查找緩存的方法回調
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果在緩存中找到了image且options選項包含SDWebImageRefreshCached,先在主線程完成一次回調,使用的是緩存中找的圖片
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// 如果在緩存中找到了image但是設置了SDWebImageRefreshCached選項,傳遞緩存的image,同時嘗試重新下載它來讓NSURLCache有機會接收服務器端的更新
completedBlock(image, nil, cacheType, YES, url);
});
}
// 如果沒有在緩存中找到image 或者設置了需要請求服務器刷新的選項,則仍需要下載
SDWebImageDownloaderOptions downloaderOptions = 0;
//開始各種options的判斷
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (image && options & SDWebImageRefreshCached) {
// 如果image已經被緩存但是設置了需要請求服務器刷新的選項,強制關閉漸進式選項
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// 如果image已經被緩存但是設置了需要請求服務器刷新的選項,忽略從NSURLCache讀取的image
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//創(chuàng)建下載操作,先使用self.imageDownloader下載
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
//如果操作取消了,不做任何事情
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
//如果我們調用completedBlock,這個block會和另外一個completedBlock爭奪一個對象,因此這個block被調用后會覆蓋新的數據
}
else if (error) {
//進行完成回調
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
//將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];
}
}
}
else {
//如果設置了下載失敗重試,將url從失敗列表中去掉
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//options包含了SDWebImageRefreshCached選項,且緩存中找到了image且沒有下載成功
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
// 圖片刷新遇到了NSSURLCache中有緩存的狀況,不調用完成回調。
}
//圖片下載成功并且 設置了需要變形Image的選項且變形的代理方法已經實現
else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
//全局隊列異步執(zhí)行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//調用代理方法完成圖片transform
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//對已經transform的圖片進行緩存
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}
//主線程執(zhí)行完成回調
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
}
//如果沒有圖片transform的需求并且圖片下載完成且圖片存在就直接緩存
else {
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);
}
});
}
}
if (finished) {
// 從正在進行的操作列表中移除這組合操作
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
//設置組合操作取消得得回調
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
//處理其他情況
//case1.在緩存中找到圖片(代理不允許下載 或者沒有設置SDWebImageRefreshCached選項 滿足至少一項)
else if (image) {
//完成回調
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
//從正在進行的操作列表中移除組合操作
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
//case2:緩存中沒有扎到圖片且代理不允許下載
else {
//主線程執(zhí)行完成回調
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
//從正在執(zhí)行的操作列表中移除組合操作
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}];
總結一下函數調用
1.先調用
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
2.設置圖片
- (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
- 2.1 取消該控件對應的之前的所有的下載操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
- 2.2 開始根據圖片的url做為key去查找
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock
2.2.1 查找內存和硬盤上的緩存
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- 2.3 創(chuàng)建下載隊列下載圖片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- 2.4 最后將進行的操作,放到view對應的opationsDicaionary的字典中。記錄當前的操作隊列
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key
以上就是源碼解析ios開發(fā)SDWebImage方法的詳細內容,更多關于ios SDWebImage方法的資料請關注腳本之家其它相關文章!
相關文章
IOS 開發(fā)之 NSMutableArray與NSArray 的區(qū)別
這篇文章主要介紹了IOS 開發(fā)之 NSMutableArray與NSArray 的區(qū)別的相關資料,希望通過本文能掌握這部分內容,需要的朋友可以參考下2017-09-09

