美文网首页
SDWebImage解析之SDWebImageDownloade

SDWebImage解析之SDWebImageDownloade

作者: DevHuangjb | 来源:发表于2019-01-04 23:28 被阅读0次

    SDWebImageDownloaderOperation是针对图片的某个具体的下载任务。SDWebImageDownloader角色则是类似于一个下载器,可以发起执行一个或多个下载任务,并对这些任务进行管理:任务的执行顺序,停止任务,取消任务,控制任务的并发量等。

    首先来看一下SDWebImageDownloader.h头文件

    定义了一个下载的配置选项SDWebImageDownloaderOptions

    typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    //下载优先级低
        SDWebImageDownloaderLowPriority = 1,
        SDWebImageDownloaderLowPriority = 2,
        //渐进式的解码下载图片
        SDWebImageDownloaderProgressiveDownload = 1 << 1,
        //配置了这个选项就会使用系统默认的缓存机制,否则使用SDImageCache缓存
        SDWebImageDownloaderUseNSURLCache = 1 << 2,
        //结合SDWebImageDownloaderUseNSURLCache选项,忽略缓存的图片
        SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
        //当app退到后台,申请后台继续下载
        SDWebImageDownloaderContinueInBackground = 1 << 4,
        //设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;
        SDWebImageDownloaderHandleCookies = 1 << 5,
        //允许非法证书请求
        SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
        //下载优先级高
        SDWebImageDownloaderHighPriority = 1 << 7,
        //如果下载的图片太大(大于60M)则对图片进行缩小
        SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
    };
    

    �下面是任务执行的顺序

    typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
    //先入先出
        SDWebImageDownloaderFIFOExecutionOrder,
        //先入后出
        SDWebImageDownloaderLIFOExecutionOrder
    };
    

    这里有个类SDWebImageDownloadToken 用于关联某个特定的SDWebImageDownloaderOperation,可以让外界方便的取消下载中的任务。

    //这个类遵守SDWebImageOperation协议,实现了cancel方法
    @interface SDWebImageDownloadToken : NSObject <SDWebImageOperation>
    //图片url
    @property (nonatomic, strong, nullable) NSURL *url;
    //SDWebImageDownloaderOperation: - (nullable id)addHandlersForProgress:completed:后返回的字典结构,里面包含这个operation的progressBlock和completedBlock
    @property (nonatomic, strong, nullable) id downloadOperationCancelToken;
    //关联的SDWebImageDownloaderOperation
    @property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperationInterface> *downloadOperation;
    @end
    
    @implementation SDWebImageDownloadToken
    - (void)cancel {
        if (self.downloadOperation) {
            SDWebImageDownloadToken *cancelToken = self.downloadOperationCancelToken;
            if (cancelToken) {
            //调用SDWebImageDownloaderOperation的 cancel:方法来移除回调函数,当所有回调函数都移除了,这个下载任务就会被取消掉
                [self.downloadOperation cancel:cancelToken];
            }
        }
    }
    @end
    

    下面来看看SDWebImageDownloader向外部暴露的属性和方法:

    @interface SDWebImageDownloader : NSObject
    //是否要对下载的图片预绘制解码
    @property (assign, nonatomic) BOOL shouldDecompressImages;
    //最大并发下载数
    @property (assign, nonatomic) NSInteger maxConcurrentDownloads;
    //当前下载任务数
    @property (readonly, nonatomic) NSUInteger currentDownloadCount;
    //超市时间,默认15s
    @property (assign, nonatomic) NSTimeInterval downloadTimeout;
    //内部用于配置NSUrlSession的NSURLSessionConfiguration
    @property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;
    //任务的执行顺序
    @property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
    //单例获取
    + (nonnull instancetype)sharedDownloader;
    //https证书
    @property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
    @property (strong, nonatomic, nullable) NSString *username;
    @property (strong, nonatomic, nullable) NSString *password;
    //用于过滤http请求头参数
    @property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
    //
    - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
    //设置http请求头参数
    - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
    //获取指定http请求头信息
    - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
    //指定自定义的下载类
    - (void)setOperationClass:(nullable Class)operationClass;
    //下载图片,设置下载选项,指定回调block
    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
    //取消
    - (void)cancel:(nullable SDWebImageDownloadToken *)token;
    //暂停
    - (void)setSuspended:(BOOL)suspended;
    //取消所有下载任务
    - (void)cancelAllDownloads;
    //重新指定NSURLSessionConfiguration配置NSUrlSession
    - (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;
    //使NSUrlSession失效,cancelPendingOperations:是否取消下载中的任务
    - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;
    
    @end
    

    接下来看看SDWebImageDownloader 的实现

    @interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
    @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
    //最后添加的任务:当指定为LIFO,如果有新下载任务,则之前最后添加的任务会依赖于新任务执行完达到LIFO效果
    @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
    //自定义的下载类
    @property (assign, nonatomic, nullable) Class operationClass;
    //管理的下载任务集合
    @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
    //request请求头
    @property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
    //保证任务集合在多线程数据安全所用的信号量
    @property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; 
    //保证HTTPHeaders在多线程数据安全所用的信号量
    @property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; 
    @property (strong, nonatomic) NSURLSession *session;
    @end
    

    在initialize中为请求添加指示器:

    + (void)initialize {
        // 我们需要额外导入SDNetworkActivityIndicator.h
        if (NSClassFromString(@"SDNetworkActivityIndicator")) {
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            //通过runtime来实例化一个指示器
            id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
    #pragma clang diagnostic pop
    
            // 因为指示器是用sharedActivityIndicator获取的单例,添加监听前先移除
            [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
            [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
            [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                     selector:NSSelectorFromString(@"startActivity")
                                                         name:SDWebImageDownloadStartNotification object:nil];
            [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                     selector:NSSelectorFromString(@"stopActivity")
                                                         name:SDWebImageDownloadStopNotification object:nil];
        }
    }
    

    下面来看构造函数:

    - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
        if ((self = [super init])) {
            _operationClass = [SDWebImageDownloaderOperation class];
            //默认预绘制解码下载图片
            _shouldDecompressImages = YES;
            //默认FIFO执行顺序
            _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
            _downloadQueue = [NSOperationQueue new];
            //默认并发数为6
            _downloadQueue.maxConcurrentOperationCount = 6;
            _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
            _URLOperations = [NSMutableDictionary new];
    #ifdef SD_WEBP
            _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
    #else
            _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
    #endif
            _operationsLock = dispatch_semaphore_create(1);
            _headersLock = dispatch_semaphore_create(1);
            //默认超时时间15s
            _downloadTimeout = 15.0;
            [self createNewSessionWithConfiguration:sessionConfiguration];
        }
        return self;
    }
    

    下面来看看如何发起和管理一个下载任务

    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock 
    

    内部调用:

    - (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                               completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                       forURL:(nullable NSURL *)url
                                               createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback
    

    来为一个SDWebImageDownloaderOperation 添加progressBlock和completedBlock。同时传递了一个createCallback来实例化一个SDWebImageDownloaderOperation。

    我们来看看如何实例化一个SDWebImageDownloaderOperation:

           __strong __typeof (wself) sself = wself;
            NSTimeInterval timeoutInterval = sself.downloadTimeout;
            if (timeoutInterval == 0.0) {
                timeoutInterval = 15.0;
            }
    
            // 是否使用系统默认的缓存策略
            NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
            NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                        cachePolicy:cachePolicy
                                                                    timeoutInterval:timeoutInterval];
            
            request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
            request.HTTPShouldUsePipelining = YES;
            if (sself.headersFilter) {
            //如果指定过滤请求头,使用block过滤
                request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
            }
            else {
                request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
            }
            SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
            operation.shouldDecompressImages = sself.shouldDecompressImages;
            
            if (sself.urlCredential) {
                operation.credential = sself.urlCredential;
            } else if (sself.username && sself.password) {
                operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
            }
            //通过下载选项配置请求优先级
            if (options & SDWebImageDownloaderHighPriority) {
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
                operation.queuePriority = NSOperationQueuePriorityLow;
            }
            
            if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            //指定LIFO
                [sself.lastAddedOperation addDependency:operation];
                sself.lastAddedOperation = operation;
            }
    
            return operation;
    

    下面来看看如何关联回调block:

    - (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                               completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                       forURL:(nullable NSURL *)url
                                               createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
        if (url == nil) {
            if (completedBlock != nil) {
                completedBlock(nil, nil, nil, NO);
            }
            return nil;
        }
        
        LOCK(self.operationsLock);
        //首先确认下这个operation是否已经在任务集合中,保证当前针对一个url只有一个下载任务,防止同一时刻反复下载一张图片
        SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
        if (!operation || operation.isFinished) {
            operation = createCallback();
            __weak typeof(self) wself = self;
            operation.completionBlock = ^{
                __strong typeof(wself) sself = wself;
                if (!sself) {
                    return;
                }
                LOCK(sself.operationsLock);
                [sself.URLOperations removeObjectForKey:url];
                UNLOCK(sself.operationsLock);
            };
            [self.URLOperations setObject:operation forKey:url];
            //添加到下载队列,开始下载
            [self.downloadQueue addOperation:operation];
        }
        UNLOCK(self.operationsLock);
        
    //为operation增加回调block
        id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        //配置SDWebImageDownloadToken,包装了回调函数,operation,url。返回给外包调用者来管理下载任务
        SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
        token.downloadOperation = operation;
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    
        return token;
    }
    

    关于NSURLSession代理方法:

    #pragma mark NSURLSessionDataDelegate
    
    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
        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);
            }
        }
    }
    

    都是通过这种形式来把代理操作转发到SDWebImageDownloaderOperation的代理方法里面去执行。

    operationWithTask通过NSURLSessionTask的taskIdentifier匹配来找出相对应的SDWebImageDownloaderOperation。

    - (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
        SDWebImageDownloaderOperation *returnOperation = nil;
        for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
            if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
                returnOperation = operation;
                break;
            }
        }
        return returnOperation;
    }
    

    相关文章

      网友评论

          本文标题:SDWebImage解析之SDWebImageDownloade

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