首先我們從常用方法入手
[self.imageview sd_setImageWithURL:[NSURL URLWithString:@"https://upload-images.jianshu.io/upload_images/1552225-abe931d3223ca900.jpg?"]
placeholderImage:[UIImage imageNamed:@"SDWebImage_logo_small"]];
跳過(guò)幾個(gè)分離的接口后鹦赎,我們進(jìn)入sd_internalSetImageWithURL方法
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
context = [context copy]; // copy to avoid mutable object
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey) {
validOperationKey = NSStringFromClass([self class]);
}
self.sd_latestOperationKey = validOperationKey;
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
// reset the progress
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}
#if SD_UIKIT || SD_MAC
// check and start image indicator
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (imageProgress) {
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
}
#if SD_UIKIT || SD_MAC
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = 0;
if (expectedSize != 0) {
progress = (double)receivedSize / expectedSize;
}
progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
#endif
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
if (!self) { return; }
// if the progress not been updated, mark it to complete state
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// check and stop image indicator
if (finished) {
[self sd_stopImageIndicator];
}
#endif
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, data, error, cacheType, finished, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = self.sd_imageTransition;
}
#endif
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
callCompletedBlockClojure();
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
#if SD_UIKIT || SD_MAC
[self sd_stopImageIndicator];
#endif
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
}
首先我們看到 [self sd_cancelImageLoadOperationWithKey:validOperationKey]谍椅, 這句代碼是干什么的先不管,繼續(xù)往下看
創(chuàng)建 SDWebImageManager
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}
進(jìn)入sharedManager古话,里初始化了下面這些東西
+ (nonnull instancetype)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
id<SDImageCache> cache = [[self class] defaultImageCache];
if (!cache) {
cache = [SDImageCache sharedImageCache];
}
id<SDImageLoader> loader = [[self class] defaultImageLoader];
if (!loader) {
loader = [SDWebImageDownloader sharedDownloader];
}
return [self initWithCache:cache loader:loader];
}
- (nonnull instancetype)initWithCache:(nonnull id<SDImageCache>)cache loader:(nonnull id<SDImageLoader>)loader {
if ((self = [super init])) {
_imageCache = cache;
_imageLoader = loader;
_failedURLs = [NSMutableSet new];
_failedURLsLock = dispatch_semaphore_create(1);
_runningOperations = [NSMutableSet new];
_runningOperationsLock = dispatch_semaphore_create(1);
}
return self;
}
繼續(xù)點(diǎn)擊進(jìn)入sharedDownloader(sharedImageCache就不在這里展示了雏吭,就先看看sharedDownloader),在initWithConfig方法里創(chuàng)建了_downloadQueue陪踩、_session杖们、_URLOperations等,這就是下載圖片的隊(duì)列和session肩狂,隊(duì)列的maxConcurrentOperationCount是6
+ (nonnull instancetype)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (nonnull instancetype)init {
return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig];
}
- (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config {
self = [super init];
if (self) {
if (!config) {
config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
}
_config = [config copy];
[_config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) options:0 context:SDWebImageDownloaderContext];
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
//中間代碼省略...
NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
if (!sessionConfiguration) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
_session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
return self;
}
回到 sd_internalSetImageWithURL方法繼續(xù)往下看摘完,創(chuàng)建完manager,到
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
//中間代碼省略...傻谁,得到的image就是我們要展示的
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
manager調(diào)用loadImageWithURL反回了一個(gè)operation <SDWebImageOperation>孝治,然后進(jìn)入sd_setImageLoadOperation方法,我們看到operation被保存了在一個(gè)通過(guò)runtime對(duì)象的關(guān)聯(lián)給imageView的一個(gè)屬性的NSMapTable類型的對(duì)象里审磁,也就是相當(dāng)于一個(gè)字典谈飒。operations的對(duì)value弱引用,當(dāng)operation被釋放的時(shí)候就會(huì)安全從operations移除态蒂。
- (SDOperationsDictionary *)sd_operationDictionary {
@synchronized(self) {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
}
對(duì)應(yīng)前面的sd_cancelImageLoadOperationWithKey方法杭措,我們應(yīng)該想到,當(dāng)cell重用時(shí)钾恢,首先通過(guò)sd_cancelImageLoadOperationWithKey方法取消之前imageView保存的operation(如果能取到的話)手素,然后設(shè)置新的operation。
繼續(xù)進(jìn)入獲取圖片的loadImageWithURL方法
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull 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
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
BOOL isFailedUrl = NO;
if (url) {
SD_LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(self.failedURLsLock);
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
return operation;
}
SD_LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(self.runningOperationsLock);
// Preprocess the options and context arg to decide the final the result for manager
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
// Start the entry to load image from cache
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];
return operation;
}
新建了一個(gè)operation< SDWebImageCombinedOperation >瘩蚪,返回給上一層刑桑,那么之前的operation<SDWebImageOperation>實(shí)際上就是這個(gè)operation,sd_cancelImageLoadOperationWithKey就是來(lái)取消這個(gè)operation的募舟。
然后operation被添加到runningOperations<NSMutableSet>,runningOperations歸屬于上面我們創(chuàng)建的manager闻察,那么這個(gè)runningOperations就是管理operation< SDWebImageCombinedOperation >了拱礁。有add對(duì)應(yīng)的肯定有remove了琢锋,在哪后面一定會(huì)看到。
SD_LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(self.runningOperationsLock);
然后進(jìn)入callCacheProcessForOperation方法呢灶,判斷要不要從緩存里獲任獬(本文主要分析下載圖片的部分,緩存這個(gè)部分基本上一筆帶過(guò))
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Check whether we should query cache
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
@weakify(operation);
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
if (!operation || operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
// Continue download process
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else {
// Continue download process
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}
如果需要鸯乃,queryImageForKey方法確認(rèn)緩存里有沒有這張圖片鲸阻,返回了一個(gè)新的operation<NSOperation>,賦值給operation.cacheOperation(operation.cacheOperation的operation是上面?zhèn)鬟M(jìn)來(lái)的缨睡,也就是sd_cancelImageLoadOperationWithKey取消的那個(gè))鸟悴。回調(diào)block里我們看到了if (!operation || operation.isCancelled)這個(gè)判斷奖年,也就是回調(diào)block還沒被執(zhí)行時(shí)细诸,cell重用了,operation.cacheOperation的operation被sd_cancelImageLoadOperationWithKey給取消了陋守,回調(diào)block再執(zhí)行時(shí)operation不存在震贵,或已經(jīng)被取消。然后被safelyRemoveOperationFromRunning方法從runningOperations里移除(對(duì)應(yīng)上面的添加)水评,直接return猩系,上一個(gè)SDWebImage的流程結(jié)束了。
如果沒有cell重用中燥,我們繼續(xù)往下走寇甸,進(jìn)入callDownloadProcessForOperation方法
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Check whether we should download image from network
BOOL shouldDownload = (options & SDWebImageFromCacheOnly) == 0;
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [self.imageLoader canRequestImageForURL:url];
if (shouldDownload) {
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.
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
// `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle
@weakify(operation);
operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// Do nothing if the operation was cancelled
// 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
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
}
} else {
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
if (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else if (cachedImage) {
[self callCompletionBlockForOperation:operation 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
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}
判斷需不需要下載,不需要下載褪那,執(zhí)行傳進(jìn)來(lái)的completedBlock幽纷,圖片加載成功。operation.cacheOperation的operation被博敬,safelyRemoveOperationFromRunning方法從runningOperations里移除友浸,SDWebImage的流程結(jié)束了。
如果需要下載偏窝,requestImageWithURL方法返回一個(gè)operation<NSOperation<SDWebImageDownloaderOperation>>收恢,賦值給operation.loaderOperation,判斷 if (!operation || operation.isCancelled) 祭往÷滓猓回調(diào)block還沒被執(zhí)行時(shí),cell重用了硼补,peration.loaderOperation的operation被sd_cancelImageLoadOperationWithKey給取消了驮肉,回調(diào)block再執(zhí)行時(shí)operation不存在,或已經(jīng)被取消已骇。然后被safelyRemoveOperationFromRunning方法從runningOperations里移除离钝,上一個(gè)SDWebImage的流程結(jié)束了票编。
如果沒有cell重用,成功下載后卵渴,執(zhí)行block內(nèi)代碼慧域,進(jìn)入callStoreCacheProcessForOperation方法處理存儲(chǔ)展示圖片。peration.loaderOperation的operation被safelyRemoveOperationFromRunning方法從runningOperations里移除浪读,SDWebImage的流程結(jié)束了昔榴。
繼續(xù)進(jìn)入下載圖片的requestImageWithURL方法,最終進(jìn)到downloadImageWithURL方法碘橘,
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
SD_LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if (!operation) {
SD_UNLOCK(self.operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
};
self.URLOperations[url] = operation;
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
[self.downloadQueue addOperation:operation];
}
else if (!operation.isExecuting) {
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
SD_UNLOCK(self.operationsLock);
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
token.downloader = self;
return token;
}
這里我們又看到一個(gè)新的operation<SDWebImageDownloaderOperation>
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
這個(gè)operation是被self.URLOperations字典管理的互订,self.URLOperations是屬于manager的imageLoader下載器的,imageLoader是屬于manager的蛹屿,也就是說(shuō)不管有多少個(gè)cell正在加載圖片屁奏,manager只有一個(gè),imageLoader只有一個(gè)错负,那么self.URLOperations也只有一個(gè)坟瓢。
如果能夠從self.URLOperations取到operation,說(shuō)明這個(gè)cell是復(fù)用的
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
};
設(shè)置operation任務(wù)完時(shí)成從URLOperations移除
self.URLOperations[url] = operation;
[self.downloadQueue addOperation:operation];
operation被添加到self.downloadQueue時(shí)就開始自動(dòng)start了犹撒,但是我們還沒有看到NSURLSessionTask什么創(chuàng)建的折联,怎么就開始下載了?所以我們應(yīng)該想到可能是operation重寫了start方法识颊,NSURLSessionTask在start方法里創(chuàng)建诚镰,并且resume, 我們先不管祥款。繼續(xù)往下看這個(gè)方法
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
token.downloader = self;
return token;
首先completedBlock是從callDownloadProcessForOperation里定義傳到這里的下載成功后存儲(chǔ)展示圖片的block(progressBlock是從最外面?zhèn)鬟M(jìn)來(lái)的清笨,也就需要我們自己實(shí)現(xiàn)的。這里我們主要關(guān)注completedBlock)刃跛,進(jìn)入addHandlersForProgress方法
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
SD_LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks];
SD_UNLOCK(self.callbacksLock);
return callbacks;
}
反回了一個(gè)callbacks字典抠艾,兩個(gè)元素對(duì)應(yīng)的值分別是progressBlock和completedBlock, operation<SDWebImageDownloaderOperation>自己也通過(guò)callbackBlocks數(shù)組保存一下桨昙。
然后創(chuàng)建了一個(gè)token检号,它的downloadOperationCancelToken就是callbacks,與operation和downloader建立關(guān)系蛙酪,返回token 齐苛,賦值給上一層的operation.loaderOperation。
到此這個(gè)downloadImageWithURL方法看完了桂塞,我們只看到在operation.completionBlock設(shè)置了operation從self.URLOperations移除凹蜂。那么如果operation沒有完成就被取消了呢?說(shuō)明其他地方也有移除。
我們?nèi)炙岩幌耈RLOperations玛痊,發(fā)現(xiàn)在SDWebImageDownloader的cancel:方法里有移除
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
NSURL *url = token.url;
if (!url) {
return;
}
SD_LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
if (operation) {
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:url];
}
}
SD_UNLOCK(self.operationsLock);
}
cancel:方法被什么時(shí)候調(diào)用呢泥彤?繼續(xù)搜cancel:在SDWebImageDownloadToken里我們發(fā)現(xiàn)調(diào)用了cancel:方法
- (void)cancel {
@synchronized (self) {
if (self.isCancelled) {
return;
}
self.cancelled = YES;
if (self.downloader) {
// Downloader is alive, cancel token
[self.downloader cancel:self];
} else {
// Downloader is dealloced, only cancel download operation
[self.downloadOperation cancel:self.downloadOperationCancelToken];
}
self.downloadOperationCancelToken = nil;
}
}
那么SDWebImageDownloadToken的- (void)cancel方法什么時(shí)候被調(diào)用的呢?我們先看一下SDWebImageDownloadToken這個(gè)類卿啡,遵守了<SDWebImageOperation>協(xié)議,實(shí)現(xiàn)了cancel方法菱父。loaderOperation是一個(gè)id<SDWebImageOperation>類型颈娜,token我們前面提到被返回賦值給了operation.loaderOperation。那么operation.loaderOperation調(diào)用cancel方法浙宜,也就是token調(diào)用了cancel方法官辽。那么loaderOperation什么時(shí)候調(diào)用cancel方法呢?
是不是它的擁有者operation取消的時(shí)候它也跟著取消呢粟瞬?
我們?cè)偃炙岩幌耹oaderOperation什么時(shí)候調(diào)用了cancel方法
@implementation SDWebImageCombinedOperation
- (void)cancel {
@synchronized(self) {
if (self.isCancelled) {
return;
}
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.loaderOperation) {
[self.loaderOperation cancel];
self.loaderOperation = nil;
}
[self.manager safelyRemoveOperationFromRunning:self];
}
}
@end
跟我們想的一樣同仆,在SDWebImageCombinedOperation(SDWebImageCombinedOperation也是遵守了<SDWebImageOperation>協(xié)議,實(shí)現(xiàn)了cancel方法)的cancel方法里有 [self.loaderOperation cancel]裙品,并且看到了safelyRemoveOperationFromRunning這句俗批。所以我們又回到了前面看過(guò)opration< SDWebImageCombinedOperation >。
所以我們?cè)購(gòu)男率崂硪幌抡麄€(gè)過(guò)程:
先回憶一下市怎,manager是一個(gè)單例岁忘,整個(gè)過(guò)程manager.runningOperations只初始化了一次,manager.imageLoader只初始化過(guò)一次区匠,manager.imageLoader.URLOperations也只初始化了一次干像,所以這幾個(gè)是管理者,不管有多少個(gè)cell加載圖片驰弄,這幾個(gè)屬性只有一個(gè)柬甥,也就是“管理者”垃你。
假設(shè)一種最麻煩的情況,一個(gè)cell正在加載一個(gè)圖片,這個(gè)圖片緩存里沒有嚎货,需要下載圖片,而這個(gè)圖片正在下載時(shí)宽菜,cell被重用了
1盼理、首先調(diào)用sd_cancelImageLoadOperationWithKey方法,方法里獲取到之前加載圖片設(shè)置的operation(SDWebImageCombinedOperation)智亮,operation調(diào)用了自己的cancel方法忆某,然后從sd_operationDictionary(runtime關(guān)聯(lián)到imageView上的屬性)移除。
2阔蛉、operation(SDWebImageCombinedOperation)的cancel方法里弃舒,自己的loaderOperation也cancel了,然后operation把自己從manager.runningOperations里移除。而loaderOperation就是token(SDWebImageDownloadToken)聋呢,也就是token調(diào)用了自己的cancel方法苗踪。token自己的cancel方法里的downloader(就是manager.imageLoader下載器)調(diào)用了cancel:方法
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
NSURL *url = token.url;
if (!url) {
return;
}
SD_LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
if (operation) {
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:url];
}
}
SD_UNLOCK(self.operationsLock);
}
3、downloader的cancel:方法里削锰,通過(guò)url從URLOperations取到上一個(gè)cell的下載圖片的operation<SDWebImageDownloaderOperation>(這個(gè)是真正的多線程的operation通铲,前面的都是抽象的)。operation調(diào)用了自己的cancel:方法器贩,參數(shù)為downloadOperationCancelToken
- (BOOL)cancel:(nullable id)token {
BOOL shouldCancel = NO;
SD_LOCK(self.callbacksLock);
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
SD_UNLOCK(self.callbacksLock);
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
傳進(jìn)來(lái)的downloadOperationCancelToken就是callbacks(兩個(gè)元素對(duì)應(yīng)的值分別是前一個(gè)cell的progressBlock和completedBlock颅夺,之前看過(guò)在哪保存的),而operation.callbackBlocks也保存了一份蛹稍。然后通過(guò)對(duì)比地址移除吧黄,如果count不為0,說(shuō)明其它的cell下載圖片也用了這個(gè)operation唆姐,就是url相同的拗慨,所以是不應(yīng)該cancel的。
4奉芦、operation<SDWebImageDownloaderOperation>如果是應(yīng)該取消的取消掉赵抢,manager.imageLoader.URLOperations移除operation。
最后仗阅,既然下載圖片是多線程的異步執(zhí)行的昌讲,通過(guò)manager.imageLoader管理,我們看一下是怎么把圖片正確的加載到對(duì)應(yīng)的cell上的减噪。每一個(gè)下載的operation<SDWebImageDownloaderOperation>短绸,在調(diào)用自己的重寫的start方時(shí),把對(duì)應(yīng)的下載task保存到了自己的dataTask屬性筹裕。下載的downloadQueue和session屬于manager.imageLoader的醋闭,我們看一下manager.imageLoader實(shí)現(xiàn)的NSURLSessionTaskDelegate代理的方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
然后進(jìn)入operationWithTask方法
- (NSOperation<SDWebImageDownloaderOperation> *)operationWithTask:(NSURLSessionTask *)task {
NSOperation<SDWebImageDownloaderOperation> *returnOperation = nil;
for (NSOperation<SDWebImageDownloaderOperation> *operation in self.downloadQueue.operations) {
if ([operation respondsToSelector:@selector(dataTask)]) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
}
return returnOperation;
}
因?yàn)閠askIdentifier是唯一的,并且在operation的start方法里對(duì)應(yīng)的task都是新初始化的朝卒,所以不會(huì)出現(xiàn)重復(fù)的taskIdentifier证逻。找到對(duì)應(yīng)的operation,調(diào)用operation實(shí)現(xiàn)的- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error方法抗斤,最后成功下載到圖片后進(jìn)入下面方法
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
最后我們執(zhí)行我們之前傳進(jìn)來(lái)的存儲(chǔ)展示圖片的completedBlock囚企。那么這個(gè)cell被復(fù)用了呢?看一下前面的第三條就知道了瑞眼,completedBlock被移除了就不會(huì)執(zhí)行了龙宏。