美文网首页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