美文网首页
35期_SDWebImage的异步下载

35期_SDWebImage的异步下载

作者: 萧修 | 来源:发表于2023-09-05 01:00 被阅读0次

SDWebImageManage中的SDImageLoader类管理任务下载

SDWebImageDownloaderSDWebImageDownloaderOperation处理图片的网络加载,在SDWebImageManage通过属性imageLoader持有SDWebImageDownloader,调用内部方法从网络加载图片。

使用SDWebImageDownloaderOperation对象下载图片

interface SDWebImageManager : NSObject

@property (strong, nonatomic, readonly, nonnull) id<SDImageLoader> imageLoader;

@end

SDWebImageDownloader

本文主要从以下几个方面了解

  • 定义SDWebImageDownloaderOptions枚举类型,设置图片从网络加载的不同情况
  • 定义并管理NSURLSession对象,通过这个对象来做网络请求,并且实现对象的代理方法
  • 定义NSURLRequest对象,管理请求头的封装
  • 对于每一个网络请求,通过一个SDWebImageDownloaderOperation自定义NSOperation来操作网络下载
  • 管理网络加载过程和完成的回调工作,通过addProgressCallback实现

枚举类型

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    SDWebImageDownloaderProgressiveDownload = 1 << 1,
    /*
     *默认情况下,http请求阻止使用NSURLCache对象。如果设置了这个标记,则NSURLCache会被http请求使用。
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,
    /*
     *如果image/imageData是从NSURLCache返回的。则completion这个回调会返回nil。
     */
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    /*
     *如果app进入后台模式,是否继续下载。这个是通过在后台申请时间来完成这个操作。如果指定的时间范围内没有完成,则直接取消下载。
     */
    SDWebImageDownloaderContinueInBackground = 1 << 4,
    /*
     处理缓存在`NSHTTPCookieStore`对象里面的cookie。通过设置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`来实现的。
     */
    SDWebImageDownloaderHandleCookies = 1 << 5,
    /*
     *允许非信任的SSL证书请求。
     *在测试的时候很有用。但是正式环境要小心使用。
     */
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
    /*
     * 默认情况下,图片加载的顺序是根据加入队列的顺序加载的。但是这个标记会把任务加入队列的最前面。
     */
    SDWebImageDownloaderHighPriority = 1 << 7,
    /*
     *默认情况下,图片会按照他的原始大小来解码显示。这个属性会调整图片的尺寸到合适的大小根据设备的内存限制。
     *如果`SDWebImageProgressiveDownload`标记被设置了,则这个flag不起作用。
     */
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
  • SDWebImageDownloader
@interface SDWebImageDownloader : NSObject <SDImageLoader>

@property (nonatomic, copy, readonly, nonnull) SDWebImageDownloaderConfig *config;

/**
 * Gets/Sets the download queue suspension state.
 */
@property (nonatomic, assign, getter=isSuspended) BOOL suspended;

/**
 * Shows the current amount of downloads that still need to be downloaded
 */
@property (nonatomic, assign, readonly) NSUInteger currentDownloadCount;

@end
  • SDWebImageDownloaderConfig
@interface SDWebImageDownloaderConfig : NSObject <NSCopying>

/**
 * The maximum number of concurrent downloads.
 * Defaults to 6.
 */
@property (nonatomic, assign) NSInteger maxConcurrentDownloads;

/**
 * The timeout value (in seconds) for each download operation.
 * Defaults to 15.0.
 */
@property (nonatomic, assign) NSTimeInterval downloadTimeout;
@end

- (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 {
                                  
                                  id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
    if (!imageLoader) {
        imageLoader = self.imageLoader;
    }
    
    operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
        
    }];
                                  

}
  • requestImageWithURL
SDWebImageDownloader.m

- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
    UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
    
    SDWebImageDownloaderOptions downloaderOptions = 0;
    if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
    if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
    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 (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
    if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;
    
    if (cachedImage && options & SDWebImageRefreshCached) {
        // force progressive off if image already cached but forced refreshing
        downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
        // ignore image read from NSURLCache if image if cached but force refreshing
        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
    }
    
    return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}

SDWebImageDownloader.m

- (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);
    id downloadOperationCancelToken;
    // 默认 URLOperations 内是没有 operation 的,operation 为空
    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,详见 1-5-7-4-5-5-1
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        // 创建 operation 出错,调用 completedBlock 回调错误信息
        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;
        }
        // self 强引用 URLOperations,URLOperations 强引用 operation,因此 operation.completionBlock  内要使用弱引用
        @weakify(self);
        operation.completionBlock = ^{
            @strongify(self);
            if (!self) {
                return;
            }
            // 当 operation 完成动作后,要将其从 URLOperations 中移除
            SD_LOCK(self.operationsLock);
            [self.URLOperations removeObjectForKey:url];
            SD_UNLOCK(self.operationsLock);
        };
        // 创建完 operation 后如果不出意外则保存在 URLOperations(可变字典) 中
        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.
        // 一定是在设置好所需的配置之后,再把 operation 添加到操作队列中
        // addOperation: 操作不会同步的执行 operation.completionBlock,因此不会引起循环引用
        // 主线:添加到队列中operation就会开始执行,开始下载;详见 1-5-7-4-5-5-3
        [self.downloadQueue addOperation:operation]; 
        // 将 progressBlock 和 completedBlock 保存起来,等待下载(NSURLSession)的代理方法来调用,详见 1-5-7-4-5-5-2
        downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    } else {
        // When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
        // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
        // 当重用 download operation 来附加更多 callbacks 的时候,由于 callbacks 的 getter 可能在其它的队列的原因可能会有线程安全问题 
        // 因此在这里给 operation 加锁,并在 SDWebImageDownloaderOperation 类中使用 @synchonzied (self),以确保这两个类的线程安全
        @synchronized (operation) {
            // 仍然参考 1-5-7-4-5-5-2
            downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        }
        // 如果 operation 未在执行,可以设置一下优先级
        if (!operation.isExecuting) {
            if (options & SDWebImageDownloaderHighPriority) {
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
                operation.queuePriority = NSOperationQueuePriorityLow;
            } else {
                operation.queuePriority = NSOperationQueuePriorityNormal;
            }
        }
    }
    SD_UNLOCK(self.operationsLock);
    // 创建一个 SDWebImageDownloadToken 实例,传入 operation;详见 1-5-7-4-5-5-4
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    // token.downloadOperationCancelToken 是弱引用,SDWebImageDownloaderOperation 的 callbackBlocks 属性才是持有它的人
    // 可以参考 1-5-7-4-5-5-2
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    
    return token;
}

相关文章

网友评论

      本文标题:35期_SDWebImage的异步下载

      本文链接:https://www.haomeiwen.com/subject/doxqmdtx.html