SDWebImage探索

·  阅读 1489

前言

SDWebImage是ios中一款优秀的图片缓存框架,已经经历了若干版本,虽说性能不是最高的,但从安全和维护角度,非常适合大多数人使用 -- SDWebImage源码

主框架介绍

其主要逻辑,如下所示

image.png

webCache: 对UIView系列的分类信息,里面包含了取消下载调用操作,调用ImageManager获取图片内容信息,以及获取到图片后的处理操作

SDWebImageManager: 用于获取图片内容的一个类,作为中间管理类的身份,来协调缓存获取和网络下载之间的操作

SDImageCache: 内存缓存和磁盘缓存的操作类,通过此类优先从内存中获取图片,其次从磁盘缓存获取内容

SDMemoryCache: 负责维护高速缓存的读取等操作

SDDiskCache: 负责维护磁盘缓存的读取等操作

SDWebImageDownLoader: 负责管理图片的下载操作,包括到SDWebImageDownloaderOperation网络请求的映射操作

SDWebImageDownloaderOperation: 自定义NSOperation的子类,用于更加精准的控制图片下载的过程,且为下载过程中网络数据的实际接收类,避免类多个数据的写入保存问题,接收数据完毕后回调内容

SDWebImage源码介绍

SDWebImage源码主要介绍上面几个类,用于了解其核心逻辑,以平时调用的分类方法为基准,依次介绍源码

且会介绍一些里面的小tips,以便于我们理解和运用

WebCache分类

通过分类的的图片加载方法调用,最终会走到下面的方法中去

- (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 {
    if (context) {
        // copy to avoid mutable object
        context = [context copy];
    } else {
        context = [NSDictionary dictionary];
    }
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    //没有key的情况保存key
    if (!validOperationKey) {
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    self.sd_latestOperationKey = validOperationKey;
    
    //取消本次key对应的operation子队列请求,避免性能浪费和错误回调,key和当前的View是绑定的
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    self.sd_imageURL = url;
    
    if (!(options & SDWebImageDelayPlaceholder)) {
        //没有设置延迟加载placeholder图片,则立即加载placeholder,在进行下载操作
        //这个是同步队列,通过queue的标签判断是否是主队列,而不是通过线程判断
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil 
                basedOnClassOrViaCustomSetImageBlock:setImageBlock 
                cacheType:SDImageCacheTypeNone imageURL:url];
        });
    }
    
    if (url) {
    
        //这里主要是设置Progress回调等相关
        ...
        
        //使用SDWebImageManager加载图片
        @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; }
            //标记progress完成
            // 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
            //根据options检查是否需要回调和主动设置image
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            //设置回调
            SDWebImageNoParamsBlock callCompletedBlockClosure = ^{
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    //直接更新view内容,在需要的时候
                    [self sd_setNeedsLayout];
                }
                //回调image和data
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            
            if (shouldNotSetImage) {
                //不需要自动设置图片,则在主队列回调
                dispatch_main_async_safe(callCompletedBlockClosure);
                return;
            }
            
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            
            ...
            //根据图片的类型,在主线程中设置图片,并回调block
            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
                callCompletedBlockClosure();
            });
        }];
        [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);
            }
        });
    }
}
复制代码

dispatch_main_async_safe

为定义的一个判断主线程,并且在主线程回调block的宏,可以看到,是根据队列的标签label来判断是否是主队列,而不是使用当前线程是否是主线程判断,由于主队列为串行队列,这样判断也方便使用宏直接调用block

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif
复制代码

SDWebImageManager图片加载管理类

其为一个图片加载管理类,通过调用SDWebImageCache和SDWebImageDownloader来实现优先缓存加载,其次网络下载

下面的方法主要是判断是否需要架子啊的逻辑,最后会进入callCacheProcessForOperation方法中去

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {、
                                        
    ...
    
    //url有问题直接失败
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
        NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
        [self callCompletionBlockForOperation:operation 
            completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain 
                code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
        return operation;
    }

    SD_LOCK(_runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(_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;
}
复制代码

加载缓存

实际的加载图片,缓存与网络请求调用逻辑在下面的方法中

- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Grab the image cache to use
    //获取SDImageCache缓存管理类
    id<SDImageCache> imageCache;
    if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
        imageCache = context[SDWebImageContextImageCache];
    } else {
        imageCache = self.imageCache;
    }
    // Get the query cache type
    SDImageCacheType queryCacheType = SDImageCacheTypeAll;
    if (context[SDWebImageContextQueryCacheType]) {
        queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
    }
    
    // Check whether we should query cache
    //判断是否需要从缓存中获取
    BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
    if (shouldQueryCache) {
        //根据url获取key
        NSString *key = [self cacheKeyForURL:url context:context];
        @weakify(operation);
        //通过key到SDImageCache类中获取缓存内容,后面介绍
        operation.cacheOperation = [imageCache queryImageForKey:key 
            options:options context:context cacheType:queryCacheType 
            completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable 
                cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            //operation不存在或者任务被取消,就什么都不做了,直接回调错误
            if (!operation || operation.isCancelled) {
                // Image combined operation cancelled by user
                [self callCompletionBlockForOperation:operation completion:completedBlock 
                    error:[NSError errorWithDomain:SDWebImageErrorDomain 
                    code:SDWebImageErrorCancelled 
                    userInfo:@{NSLocalizedDescriptionKey : 
                        @"Operation cancelled by user during querying the cache"}] 
                    url:url];
                [self safelyRemoveOperationFromRunning:operation];
                return;
            } else if (context[SDWebImageContextImageTransformer] && !cachedImage) {
                //查询原始图,代替下载的,如果原始图查询失败,则才开始下载,由于逻辑与方法基本一致,就介绍了
                // Have a chance to query original cache instead of downloading
                [self callOriginalCacheProcessForOperation:operation 
                    url:url options:options context:context progress:progressBlock 
                    completed:completedBlock];
                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];
    }
}
复制代码

从网络下载图片

从网络下载图片的调用方法如下所示

// Download process
- (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 {
    // Grab the image loader to use
    //获取实现ImageLoader协议的代理类,来调用下载方法
    id<SDImageLoader> imageLoader;
    if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
        imageLoader = context[SDWebImageContextImageLoader];
    } else {
        imageLoader = self.imageLoader;
    }
    
    // Check whether we should download image from network
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] 
        || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
        shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
    } else {
        shouldDownload &= [imageLoader canRequestImageForURL:url];
    }
    //需要下载则开始下载图片
    if (shouldDownload) {
        if (cachedImage && options & SDWebImageRefreshCached) {
            [self callCompletionBlockForOperation:operation completion:completedBlock 
                image:cachedImage data:cachedData error:nil cacheType:cacheType 
                finished:YES url:url];
            SDWebImageMutableContext *mutableContext;
            if (context) {
                mutableContext = [context mutableCopy];
            } else {
                mutableContext = [NSMutableDictionary dictionary];
            }
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        
        @weakify(operation);
        //下载图片方法调用
        operation.loaderOperation = [imageLoader requestImageWithURL:url 
            options:options context:context progress:progressBlock completed:^(UIImage 
                *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
            @strongify(operation);
            //取消下载失败直接回调
            if (!operation || operation.isCancelled) {
                ...
            } else {
                //到这里就是下载成功了
                //如果下载成功则从失败名单删除
                if ((options & SDWebImageRetryFailed)) {
                    SD_LOCK(self->_failedURLsLock);
                    [self.failedURLs removeObject:url];
                    SD_UNLOCK(self->_failedURLsLock);
                }
                // Continue store cache process
                //对缓存进行处理,包括对旋转图片恢复,磁盘缓存等
                [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];
    }
}
复制代码

SDImageCache快速缓存管理类

其第一个方法主要是判断options的不介绍,主要是下面的方法

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key 
    options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context 
    cacheType:(SDImageCacheType)queryCacheType 
    done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    //key不存在直接返回空,相当于找不到
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // Invalid cache type 没有缓存直接结束
    if (queryCacheType == SDImageCacheTypeNone) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    
    // First check the in-memory cache...
    //如果不是只存放磁盘,则开始从高速缓存内存中获取
    UIImage *image;
    if (queryCacheType != SDImageCacheTypeDisk) {
        image = [self imageFromMemoryCacheForKey:key];
    }
    //获取到图片
    if (image) {
        //对图片进行处理,包括旋转图片(图片获取到可能是旋转的)
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // Ensure static image
            Class animatedImageClass = image.class;
            if (image.sd_isAnimated || 
            ([animatedImageClass isSubclassOfClass:[UIImage class]] && 
                [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MAC
                image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale 
                    orientation:kCGImagePropertyOrientationUp];
#else
                image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale 
                    orientation:image.imageOrientation];
#endif
            }
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // Check image class matching
            Class animatedImageClass = image.class;
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
                image = nil;
            }
        }
    }
    
    //如果仅仅从高速缓存中获取,则直接结束
    BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || 
        (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    //高速缓存没有找到,则开始在磁盘缓存
    // Second check the disk cache...
    NSOperation *operation = [NSOperation new];
    // Check whether we need to synchronously query disk
    // 1. in-memory cache hit & memoryDataSync
    // 2. in-memory cache miss & diskDataSync
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    //定义block,用于io调用,访问磁盘和内存写入
    void(^queryDiskBlock)(void) =  ^{
        if (operation.isCancelled) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return;
        }
        
        @autoreleasepool {
            //从磁盘查询图片数据
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            if (image) {
                // the image is from in-memory cache, but need image data
                //如果从内存中找到赋值,后续直接返回
                diskImage = image;
            } else if (diskData) {
                //磁盘找到的数据,保存到高速缓存中去
                BOOL shouldCacheToMomery = YES;
                if (context[SDWebImageContextStoreCacheType]) {
                    SDImageCacheType cacheType = 
                        [context[SDWebImageContextStoreCacheType] integerValue];
                    shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || 
                        cacheType == SDImageCacheTypeMemory);
                }
                // decode image data only if in-memory cache missed
                diskImage = [self diskImageForKey:key data:diskData 
                    options:options context:context];
                if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = diskImage.sd_memoryCost;
                    [self.memoryCache setObject:diskImage forKey:key cost:cost];
                }
            }
           
            if (doneBlock) {
                if (shouldQueryDiskSync) {
                    //根据需要在io队列中回调
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                } else {
                    //在主线程中回调内容
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                    });
                }
            }
        }
    };
    
    // Query in ioQueue to keep IO-safe
    //在io queue中执行block
    if (shouldQueryDiskSync) {
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    
    return operation;
}
复制代码

SDWebImageDownloader、SDWebImageDownloaderOperation

SDWebImageDownloader与SDWebImageDownloaderOperation共同完成下载功能

SDWebImageDownloader负责队列的设置和网络请求的代理回调操作

SDWebImageDownloaderOperation继承NSOperation,且代理了SDWebImageDownloader的网络请求操作,负责请求的开始下载取消等操作

SDWebImageDownloader

创建新任务,加入到下载队列中

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    ...
    //创建新任务,加入到下载队列中
    if (!operation || operation.isFinished || operation.isCancelled) {
        //设置request等相关参数
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        if (!operation) {
            SD_UNLOCK(_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;
        [self.downloadQueue addOperation:operation];
    } else {
        /
        @synchronized (operation) {
            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        }
        if (!operation.isExecuting) {
            if (options & SDWebImageDownloaderHighPriority) {
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
                operation.queuePriority = NSOperationQueuePriorityLow;
            } else {
                operation.queuePriority = NSOperationQueuePriorityNormal;
            }
        }
    }
    SD_UNLOCK(_operationsLock);
    
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    
    return token;
}
复制代码

NSURLSession代理操作

下面可以看到,请求根据保存的队列,将子任务取出,将回调方法对应到对应的子任务中代理处理,避免了复杂的数据保存,任务分配等问题

这里只简单介绍SDWebImageDownloader的delegate就不介绍SDWebImageDownloaderOperation中的代理回调了

#pragma mark NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
        [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
    } else {
        if (completionHandler) {
            completionHandler(NSURLSessionResponseAllow);
        }
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
        [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {

    // Identify the operation that runs this task and pass it the delegate method
    NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
    if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
        [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
    } else {
        if (completionHandler) {
            completionHandler(proposedResponse);
        }
    }
}

#pragma mark 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];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    
    // 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:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
        [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
    } else {
        if (completionHandler) {
            completionHandler(request);
        }
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

    // 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:didReceiveChallenge:completionHandler:)]) {
        [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
    } else {
        if (completionHandler) {
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        }
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
    
    // 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:didFinishCollectingMetrics:)]) {
        [dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
    }
}
复制代码

SDWebImageDownloaderOperation重写

重写了start方法,以便于更好的使用NSOperation

- (void)start {
    @synchronized (self) {
        //如果任务已经取消,则结束
        if (self.isCancelled) {
            if (!self.isFinished) self.finished = YES;
            // Operation cancelled by user before sending the request
            [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
            [self reset];
            return;
        }

#if SD_UIKIT
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak typeof(self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                [wself cancel];
            }];
        }
#endif
        //开始已下载操作
        NSURLSession *session = self.unownedSession;
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;
        }
        
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // Grab the cached data for later check
            NSURLCache *URLCache = session.configuration.URLCache;
            if (!URLCache) {
                URLCache = [NSURLCache sharedURLCache];
            }
            NSCachedURLResponse *cachedResponse;
            // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
            @synchronized (URLCache) {
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
                self.response = cachedResponse.response;
            }
        }
        
        if (!session.delegate) {
            // Session been invalid and has no delegate at all
            [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Session delegate is nil and invalid"}]];
            [self reset];
            return;
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }
    //根据枚举设置任务优先级等
    if (self.dataTask) {
        if (self.options & SDWebImageDownloaderHighPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityHigh;
            self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive;
        } else if (self.options & SDWebImageDownloaderLowPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityLow;
            self.coderQueue.qualityOfService = NSQualityOfServiceBackground;
        } else {
            self.dataTask.priority = NSURLSessionTaskPriorityDefault;
            self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
        }
        //开始任务
        [self.dataTask resume];
        //回调progress
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        __block typeof(self) strongSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
        });
    } else {
        //任务出错,结束
        if (!self.isFinished) self.finished = YES;
        [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
        [self reset];
    }
}
复制代码

重写了NSOperation的cancel方法

- (void)cancel {
    @synchronized (self) {
        [self cancelInternal];
    }
}

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel];
    
    __block typeof(self) strongSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] 
            postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
    });
    
    //取消task任务操作
    if (self.dataTask) {
        [self.dataTask cancel];
        self.dataTask = nil;
    }
    //调整执行状态和完成状态
    if (self.isExecuting || self.isFinished) {
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }
    
    // Operation cancelled by user during sending the request
    //结束回调
    [self callCompletionBlocksWithError:
        [NSError errorWithDomain:SDWebImageErrorDomain 
            code:SDWebImageErrorCancelled 
            userInfo:@{NSLocalizedDescriptionKey : 
                @"Operation cancelled by user during sending the request"}]];

    [self reset];
}
复制代码

可以看到,为了保证属性能够触发KVC,加入了手动KVC操作


- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}
复制代码

最后

SDWebImage中的下载流程我相信通过简介图就能看到,作者将一个功能拆分出各个模块,通过管理类进行协调调用,例如:SDWebImageManager负责管理缓存类和网络请求类的调用,SDImageCache负责快速缓存和磁盘缓存的处理等

下载模块,通过重写NSOperation,并且将下载任务映射到子任务中去,避免了在一个类中管理多个下载任务的数据以及其他回调等

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改