//通过url创建一个Operation - (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. //如果传入的url是字符串类型,则将url转换成NSURL类型(容错处理) if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; }
// Prevents app crashing on argument type error like sending NSNull instead of NSURL //如果经过上述处理后的url还是不是NSURL类型,那么将url设置为nil. if (![url isKindOfClass:NSURL.class]) { url = nil; }
BOOL isFailedUrl = NO; if (url) { //创建一个互斥锁防止,保证线程安全 @synchronized (self.failedURLs) { //判断url是否是下载失败过, isFailedUrl = [self.failedURLs containsObject:url]; } } //如果url不存在那么直接返回一个block,如果url存在那么继续 //如果options的值没有设置为失败后重试并且url下载失败过,执行完成block返回错误 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; } //将operations加入到runningOperations数组中 @synchronized (self.runningOperations) { [self.runningOperations addObject:operation]; } //根据url获取到对应的key NSString *key = [self cacheKeyForURL:url]; //根据key异步在缓存中查找是否有图片,且返回一个operation operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { //如果operation取消了,将它从self.runningOperations中移除 if (operation.isCancelled) { [self safelyRemoveOperationFromRunning:operation]; return; } //条件1.在缓存中没有找到图片或者options设置为了SDWebImageRefreshCached //条件2.代理允许下载,或者代理不响应imageManager:shouldDownloadImageForURL:方法 if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { //如果在缓存中找到了图片,但是需要更新缓存 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,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新 [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; }
// download if no image or requested to refresh anyway, and download allowed by delegate //设置downloaderOptions 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 downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } //到这里才真的去下载图片... SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { __strong __typeof(weakOperation) strongOperation = weakOperation; //如果操作取消,do nothing if (!strongOperation || strongOperation.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 } //如果有错误,将error回调出去 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 && error.code != NSURLErrorNetworkConnectionLost) { @synchronized (self.failedURLs) { //将url添加到失败列表中、 [self.failedURLs addObject:url]; } } } else { //如果设置了下载失败重试,则将url从列表中移除、 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), ^{ //调用代理方法完成图片的transform UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; //对已经transform的图片进行缓存 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]; } } //如果已经完成下载了,将operation移除 if (finished) { [self safelyRemoveOperationFromRunning:strongOperation]; } }]; @synchronized(operation) { // Need same lock to ensure cancelBlock called because cancel method can be called in different queue 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]; //移除operation [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]; //移除operation [self safelyRemoveOperationFromRunning:operation]; } }];
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock { // check if the operation is already cancelled, then we just call the cancelBlock if (self.isCancelled) { if (cancelBlock) { cancelBlock(); } _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes } else { _cancelBlock = [cancelBlock copy]; } }