美文网首页iOS
《重读SDWebImage》-Downloader

《重读SDWebImage》-Downloader

作者: 我是繁星 | 来源:发表于2019-03-24 14:57 被阅读25次

    概述

    关于SDWebImage下载器的大概流程如下,总体上SDWebImageDownloader负责下载任务的调度,SDWebImageDownloaderOperation负责具体的下载任务。

    流程.png

    一、SDWebImageDownloaderOperation

    SDWebImageDownloaderOperation其实就是一个下载任务的抽象,继承自NSOperation,我们就复习一下,NSOperation的知识。

    1.1、NSOperation的生命周期

    说到NSOperation不得不说两个方法,-main()-start()两个方法,他们有什么区别呢,区别如下:

    • -main():当main执行完毕后,finish就会主动置为空,当前op出队列,下一个OP开始执行。
    • -start():start方法执行完毕,并不会影响当前op的生命周期,我们可以通过重写属性,来控制成员变量finish的值,当符合结束条件时finish置为yes,就会主动执行下一个任务。

    1.2、再复习一个知识点,因为原本NSOperation中finish是只读的,如何重写一个finish属性使其实内部可读写,外部还是只读的呢

    @interface TestOperationOne()
    //写在类扩展里
    @property (assign, nonatomic, getter = isFinished) BOOL finished;
    @end
    @implementation TestOperationOne
    //写在实现中
    @synthesize finished = _finished;
    @end
    //重写setter方法是为了支持KVO
    - (void)setFinished:(BOOL)finished {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }
    
    
    

    如上是实现原理,先复习一下这两个关键字的作用

    • @property默认生成setter个getter方法,而且会检测是否有(_属性名)对应的成员变量,如果没有会自动生成。
    • @synthesize可以使属性关联到对应的属性上,这样setter和getter方法操作的就是关联的成员变量了。
      如果我们只写了重写了finished
    @interface TestOperationOne()
    @property (assign, nonatomic, getter = isFinished) BOOL finished;
    @end
    

    会报警告


    image.png

    为什么呢,因为我们重写了@property会默认将我们写的finish与_finish这个成员变量绑定,但是_finish已经被他的父类NSOperation关联了,编译器不知道怎么办了,该类中的finished没有关联的成员变量了。需要我们制定以下@synthesize finished = _finishedOK啦。

    1.3、初始化方法
    //传入Request和session还有下载选项,并保存
    - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                                  inSession:(nullable NSURLSession *)session
                                    options:(SDWebImageDownloaderOptions)options {
        if ((self = [super init])) {
            //存储信息
            _request = [request copy];
            _shouldDecompressImages = YES;
            _options = options;
            _callbackBlocks = [NSMutableArray new];
            _executing = NO;
            _finished = NO;
            _expectedSize = 0;
            _unownedSession = session;
            _callbacksLock = dispatch_semaphore_create(1);
            //创建同步队列
            _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
    #if SD_UIKIT
            _backgroundTaskId = UIBackgroundTaskInvalid;
    #endif
        }
        return self;
    }
    
    1.4、回调block的处理

    添加需要回调的block,通过copy可以将block拷贝到堆内存中,用引用计数管理内存,最终block会存储在一个字典中@[kProgressCallbackKey:progressBlock,kCompletedCallbackKey:completedBlock]以这样的形式,并且添加到self.callbacksLock数组中,为什么这样这样做呢??
    这是因为DSWebImageDownloader中会根据传入的URL判断这个URL是否正在下载,如果正在下载则不会重复下载任务,会把block传入对应的OP中并存储到self.callbacksLock中,回调的时候会将数组self.callbacksLock中的block全部进行回调。
    说白了就是做了一个下载的去重逻辑。并对重复调用下载的地方也同样进行回调

    //添加需要回调的block
    - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
        //创建可变字典callbacks存储[kProgressCallbackKey:progressBlock,
        //                                                 kCompletedCallbackKey:completedBlock]
        SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        LOCK(self.callbacksLock);
        [self.callbackBlocks addObject:callbacks];
        UNLOCK(self.callbacksLock);
        return callbacks;
    }
    
    1.5、SDWebImageDownloaderOperation的相关操作
    • - (void)start任务开始,在OP对象被添加到队列中时会自动调用该方法。是下载操作的开端。
    //开始
    - (void)start {
        //加锁保护
        @synchronized (self) {
            if (self.isCancelled) {
                self.finished = YES;
                [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)];
                //beginBackgroundTaskWithExpirationHandler会像系统申请后台额外时间,如果额外时间还没有干完,就进入handle回调,处理进入后台。
                self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                    [wself cancel];
                }];
            }
    #endif
            NSURLSession *session = self.unownedSession;
            if (!session) {
                //如果会话为nil,则重新创建
                NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
                sessionConfig.timeoutIntervalForRequest = 15;
                
                /**
                 *  为这个任务创建会话
                 *  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];
                //注意这里面创建的保存在ownedSession里面了,因为这个是OP自己创建的session,不用的时候要置空,后面会讲到
                self.ownedSession = session;
            }
            //如果选项包含忽略缓存的response
            if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
                // 获取缓存的数据供以后检查
                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
                // cachedResponseForRequest不安全获取cacheResponse
                @synchronized (URLCache) {
                    cachedResponse = [URLCache cachedResponseForRequest:self.request];
                }
                if (cachedResponse) {
                    //存储缓存的response数据
                    self.cachedData = cachedResponse.data;
                }
            }
            //创建任务,在URL会话中执行的任务,如下载特定资源。
            self.dataTask = [session dataTaskWithRequest:self.request];
            NSLog(@"session:%@ dataTask:%@",self.unownedSession,self.dataTask);
            //维护执行状态
            self.executing = YES;
        }
        if (self.dataTask) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunguarded-availability"
            //通过优先级处理
            if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
                //高优先级
                if (self.options & SDWebImageDownloaderHighPriority) {
                    self.dataTask.priority = NSURLSessionTaskPriorityHigh;
                    //低优先级
                } else if (self.options & SDWebImageDownloaderLowPriority) {
                    self.dataTask.priority = NSURLSessionTaskPriorityLow;
                }
            }
    #pragma clang diagnostic pop
            [self.dataTask resume];
            //遍历所有的progressBlock发送消息0
            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 {
            [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
            [self done];
        }
    }
    
    • - (void)cancel任务取消,这里将finish置为YES,当前OP出队列
    //op取消
    - (void)cancel {
        @synchronized (self) {
            [self cancelInternal];
        }
    }
    //取消内部的操作
    - (void)cancelInternal {
        if (self.isFinished) return;
        [super cancel];
    
        if (self.dataTask) {
            //取消下载任务
            [self.dataTask cancel];
            __block typeof(self) strongSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                //发送下载任务通知
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
            });
            //设置
            if (self.isExecuting) self.executing = NO;
            if (!self.isFinished) self.finished = YES;
        }
    
        [self reset];
    }
    
    • - (void)done任务完成,finish也置为YES,OP出队列
    //结束
    - (void)done {
        //设置finish
        self.finished = YES;
        //设置excuting
        self.executing = NO;
        [self reset];
    }
    
    • - (void)reset任务重置,
    //重置数据
    - (void)reset {
        LOCK(self.callbacksLock);
        //删除callbackBlocks中保存的block
        [self.callbackBlocks removeAllObjects];
        UNLOCK(self.callbacksLock);
        
        @synchronized (self) {
            //清理sessionTask
            self.dataTask = nil;
            //清楚session
            if (self.ownedSession) {
                [self.ownedSession invalidateAndCancel];
                self.ownedSession = nil;
            }
           
    #if SD_UIKIT
            //这里判断系统返回的后台任务标志是否有效,如果有效调用endBackgroundTask方法,和beginBackgroundTaskWithExpirationHandler承兑出现
            if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
                // If backgroundTaskId != UIBackgroundTaskInvalid, sharedApplication is always exist
                UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
                [app endBackgroundTask:self.backgroundTaskId];
                self.backgroundTaskId = UIBackgroundTaskInvalid;
            }
    #endif
        }
    }
    
    1.6、URSSessionDelegate的处理

    session任务获得初始的回复,处理逻辑,这里面处理了304的情况

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
        
        NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
        //数据的大小
        NSInteger expected = (NSInteger)response.expectedContentLength;
        //容错处理
        expected = expected > 0 ? expected : 0;
        //数据大小
        self.expectedSize = expected;
        //存储response
        self.response = response;
        //获取状态码
        NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
        //如果小于400 valid设置为yes
        BOOL valid = statusCode < 400;
        //在客户端有缓存的时候,第二次请求会在header中传上一期请求的时间If-Modified-Since 和 If-None-Match,服务器判断数据是否有修改,如果没有直接返回304 not Modified 并且没有响应体,所以这里判断服务器返回304,且没有缓存数据的时候是异常情况。
        if (statusCode == 304 && !self.cachedData) {
            valid = NO;
        }
        if (valid) {
            //获取存储的block设置回调
            for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
                progressBlock(0, expected, self.request.URL);
            }
        } else {
            // 如果状态错误那么设置为取消
            // Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle
            disposition = NSURLSessionResponseCancel;
        }
        __block typeof(self) strongSelf = self;
        //发送通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:strongSelf];
        });
        
        if (completionHandler) {
            completionHandler(disposition);
        }
    }
    

    会话获取数据时回调,这里如果选择了渐进解码,会不断的回调completeBlock。

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        //创建一个预期大小的data
        if (!self.imageData) {
            self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
        }
        //data中添加数据
        [self.imageData appendData:data];
        //选项判断,是否渐进式下载
        if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
            // 获取图片数据
            __block NSData *imageData = [self.imageData copy];
            // 获取数据的所有大小
            const NSInteger totalSize = imageData.length;
            // f判断是否下载完成
            BOOL finished = (totalSize >= self.expectedSize);
            if (!self.progressiveCoder) {
                //这里需要初始化一个新的渐进解码器
                for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) {
                    if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
                        [((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
                        self.progressiveCoder = [[[coder class] alloc] init];
                        break;
                    }
                }
            }
            
            // 在全局的同步队列中进行解码
            dispatch_async(self.coderQueue, ^{
                @autoreleasepool {
                    UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
                    if (image) {
                        NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                        image = [self scaledImageForKey:key image:image];
                        if (self.shouldDecompressImages) {
                            image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
                        }
                        
                        // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
                        [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
                    }
                }
            });
        }
        //做回调
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
        }
    }
    

    下载完成的回调

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        //数据操作互斥锁
        @synchronized(self) {
            self.dataTask = nil;
            __block typeof(self) strongSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
                if (!error) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:strongSelf];
                }
            });
        }
        
        // make sure to call `[self done]` to mark operation as finished
        if (error) {
            [self callCompletionBlocksWithError:error];
            [self done];
        } else {
            //如果存在block,那么解码
            if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
                /**
                 *  If you specified to use `NSURLCache`, then the response you get here is what you need.
                 */
                __block NSData *imageData = [self.imageData copy];
                self.imageData = nil;
                if (imageData) {
                    /**  if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
                     *  then we should check if the cached data is equal to image data
                     */
                    //这里判断是缓存数据
                    if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
                        // call completion block with nil
                        [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
                        [self done];
                    } else {
                        // 解码数据
                        dispatch_async(self.coderQueue, ^{
                            @autoreleasepool {
                                //数据解码
                                UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
                                //返回缓存的Key
                                NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                                //调整图片倍率
                                image = [self scaledImageForKey:key image:image];
                                
                                // 对于gif不强制解码
                                // because there has imageCoder which can change `image` or `imageData` to static image, lose the animated feature totally.
                                BOOL shouldDecode = !image.images && image.sd_imageFormat != SDImageFormatGIF;
                                if (shouldDecode) {
                                    if (self.shouldDecompressImages) {
                                        //图片的解压缩
                                        BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
                                        image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
                                    }
                                }
                                CGSize imageSize = image.size;
                                if (imageSize.width == 0 || imageSize.height == 0) {
                                    [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                                } else {
                                    //返回完成回调
                                    [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
                                }
                                [self done];
                            }
                        });
                    }
                } else {
                    [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
                    [self done];
                }
            } else {
                [self done];
            }
        }
    }
    

    二、SDWebImageDownloader

    接下来看看细节从SDWebImageDownloader开始
    先看看下载器支持的操作,很多哦,都在这个枚举里面呢,按位枚举可以或或或

    typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
        /**
         * 设置低优先级
         */
        SDWebImageDownloaderLowPriority = 1 << 0,
        
        /**
         * 采用渐进式下载,就是一点一点显示的辣种。
         */
        SDWebImageDownloaderProgressiveDownload = 1 << 1,
    
        /**
         * 使用URL的缓存
         */
        SDWebImageDownloaderUseNSURLCache = 1 << 2,
    
        /**
         * 忽略缓存的响应
         * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
         */
        SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
        
        /**
         * 支持后台下载
         */
        SDWebImageDownloaderContinueInBackground = 1 << 4,
    
        /**
         * 设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;
         */
        SDWebImageDownloaderHandleCookies = 1 << 5,
    
        /**
         * 允许不受信任的SSL证书
         */
        SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
    
        /**
         * 设置高优先级
         */
        SDWebImageDownloaderHighPriority = 1 << 7,
        
        /**
         * 按比例缩小图片
         */
        SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
    };
    

    还有一个枚举,是任务的处理顺序。

    typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
        /**
         * 默认是队列形式的先进先出
         */
        SDWebImageDownloaderFIFOExecutionOrder,
    
        /**
         * 栈类型的后进先出
         */
        SDWebImageDownloaderLIFOExecutionOrder
    };
    
    1.1、初始化

    初始化方法负责初始化各种条件变量

    • session 负责协调一组相关网络传输任务的对象,在下载器中所有的下载任务默认都是这个session负责生成下载任务。
    • _downloadQueue 一个NSOperationQueue负责OP的分发调配。
    • _URLOperations 存储当前正在执行的OP,为了给当前正在执行的OP下发网络回调。
    • _HTTPHeaders 设置,默认设置了UA和Accept
    - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
        if ((self = [super init])) {
            //设置op的class
            _operationClass = [SDWebImageDownloaderOperation class];
            //是否解压图片
            _shouldDecompressImages = YES;
            //操作顺序
            _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
            //下载队列
            _downloadQueue = [NSOperationQueue new];
            //最大并发
            _downloadQueue.maxConcurrentOperationCount = 6;
            _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
            //url操作
            _URLOperations = [NSMutableDictionary new];
            //headerDix
            SDHTTPHeadersMutableDictionary *headerDictionary = [SDHTTPHeadersMutableDictionary dictionary];
            //设置UA
            NSString *userAgent = nil;
    #if SD_UIKIT
            // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
            userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
    #elif SD_WATCH
            // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
            userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
    #elif SD_MAC
            userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
    #endif
            if (userAgent) {
                if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
                    NSMutableString *mutableUserAgent = [userAgent mutableCopy];
                    if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                        userAgent = mutableUserAgent;
                    }
                }
                headerDictionary[@"User-Agent"] = userAgent;
            }
    #ifdef SD_WEBP
            headerDictionary[@"Accept"] = @"image/webp,image/*;q=0.8";
    #else
            headerDictionary[@"Accept"] = @"image/*;q=0.8";
    #endif
            _HTTPHeaders = headerDictionary;
            _operationsLock = dispatch_semaphore_create(1);
            _headersLock = dispatch_semaphore_create(1);
            _downloadTimeout = 15.0;
    
            [self createNewSessionWithConfiguration:sessionConfiguration];
        }
        return self;
    }
    
    - (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
        [self cancelAllDownloads];
    
        if (self.session) {
            [self.session invalidateAndCancel];
        }
        //设置超时时间
        sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
    
        /**
         *  创建session
         *  我们将nil作为委托队列发送,以便会话创建一个串行操作队列来执行所有委托
         *  method calls and completion handler calls.
         */
        self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                     delegate:self
                                                delegateQueue:nil];
    }
    
    1.2、创建SDWebImageDownloaderOperation

    主要是处理了一些我们传入的网络相关的选项

    • SDWebImageDownloaderUseNSURLCache 是否用request缓存
    • SDWebImageDownloaderHandleCookies 是否处理cookie
    • SDWebImageDownloaderAllowInvalidSSLCertificates 是否允许SSL证书不受信任
    //创建一个根据URL创建一个下载OP
    - (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
                                                                                      options:(SDWebImageDownloaderOptions)options {
        NSTimeInterval timeoutInterval = self.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }
    
        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise‘
        //缓存规则是否使用URL缓存
        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        //设置urlRequest
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];
        //处理cookie
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        
        request.HTTPShouldUsePipelining = YES;
        if (self.headersFilter) {
            request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
        }
        else {
            request.allHTTPHeaderFields = [self allHTTPHeaderFields];
        }
        NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
        operation.shouldDecompressImages = self.shouldDecompressImages;
        
        if (self.urlCredential) {
            operation.credential = self.urlCredential;
        } else if (self.username && self.password) {
            operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        
        if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [self.lastAddedOperation addDependency:operation];
            self.lastAddedOperation = operation;
        }
    
        return operation;
    }
    
    1.3、下载任务
    //添加下载任务并且返回一个downloadToken
    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
        // 由于没有完成回调不会返回图片数据,所以没有complete是没有意义的
        if (url == nil) {
            if (completedBlock != nil) {
                completedBlock(nil, nil, nil, NO);
            }
            return nil;
        }
        
        LOCK(self.operationsLock);
        //获取出OP的类型URLOperations是正在执行的OP
        NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
        //如果没有url对应的OP,或者任务完成,任务取消
        if (!operation || operation.isFinished || operation.isCancelled) {
            //创建一个OP
            operation = [self createDownloaderOperationWithUrl:url options:options];
            __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);
            };
            //添加进URLOperations中
            [self.URLOperations setObject:operation forKey:url];
            // 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.
            //添加进OP
            [self.downloadQueue addOperation:operation];
        }
        else if (!operation.isExecuting) {
            //如果OP没有正在执行,说明正在等待下载,设置重新设置一下优先级
            if (options & SDWebImageDownloaderHighPriority) {
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
                operation.queuePriority = NSOperationQueuePriorityLow;
            } else {
                operation.queuePriority = NSOperationQueuePriorityNormal;
            }
        }
        UNLOCK(self.operationsLock);
        //创建一个广告删除的token
        id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
        
        SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
        token.downloadOperation = operation;
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    
        return token;
    }
    

    相关文章

      网友评论

        本文标题:《重读SDWebImage》-Downloader

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