美文网首页
SDWebImage解析之SDWebImageDownloade

SDWebImage解析之SDWebImageDownloade

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

    根据我阅读源码的习惯,我会先阅读比较核心的,并且没有或者较少引入其他文件的类。所以,就让我们先从SDWebImageDownloaderOperation说起吧。

    在iOS9.0之后,苹果推出了NSUrlSession,并推荐开发者用NSUrlSession替代NSUrlConnection。如果我们自己用NSUrlSession来请求图片,主要有:

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pic27.nipic.com/20130329/890845_115317964000_2.jpg"]];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
    [dataTask resume];
    

    delegateQueue:为执行代理方法的队列,不指定session默认会创建一个serial operation queue

    在回调的代理方法中处理数据

    #pragma mark NSURLSessionDataDelegate
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        [self.imageData appendData:data];
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        UIImage *image = [UIImage imageWithData:self.imageData];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    }
    

    SDWebImageDownloaderOperation是NSOperaion的子类,就是用来封装上面的下载操作

    先来看看SDWebImageDownloaderOperation.h

    文件一开始向外部暴露了四个通知:

    //下载开始
    FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
    //接受到服务器响应
    FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
    //下载停止
    FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
    //下载结束
    FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
    

    在UIView+WebCache分类中,就是对SDWebImageDownloadStartNotification和SDWebImageDownloadFinishNotification通知的监听来添加和移除UIActivityIndicatorView 。

    在SDWebImageDownloaderOperation.h头文件中,定义了SDWebImageDownloaderOperationInterface 协议。我们可以继承NSOperation同时遵守SDWebImageDownloaderOperationInterface协议来自定义下载操作类。

    @protocol SDWebImageDownloaderOperationInterface<NSObject>
    - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                                  inSession:(nullable NSURLSession *)session
                                    options:(SDWebImageDownloaderOptions)options;
    //为下载操作添加下载中和下载完成的回调
    - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
    //是否需要对下载的图片进行预解码
    - (BOOL)shouldDecompressImages;
    - (void)setShouldDecompressImages:(BOOL)value;
    //https证书
    - (nullable NSURLCredential *)credential;
    - (void)setCredential:(nullable NSURLCredential *)value;
    //取消下载操作
    - (BOOL)cancel:(nullable id)token;
    @end
    

    在SDWebImageDownloaderOperation类的私有拓展中包含以下属性:

    //包含下载操作回调block的数组
    @property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
    //下载运行中
    @property (assign, nonatomic, getter = isExecuting) BOOL executing;
    //下载结束
    @property (assign, nonatomic, getter = isFinished) BOOL finished;
    //拼接的数据
    @property (strong, nonatomic, nullable) NSMutableData *imageData;
    //使用NSUrlCache缓存的数据
    @property (copy, nonatomic, nullable) NSData *cachedData;
    //外部注入的NSURLSession,因为在SDWebImageDownloader中已经对这个session强引用,所有使用weak
    @property (weak, nonatomic, nullable) NSURLSession *unownedSession;
    //如果外部没有注入session,内部会自己实例化一个session,并负责这个session的生命周期
    @property (strong, nonatomic, nullable) NSURLSession *ownedSession;
    //通过UrlRequest生成的dataTask请求任务
    @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
    //为保证在多线程环境下操作callbackBlocks的数据安全提供的锁
    @property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock; 
    //对图片进行解码的队列
    @property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue;
    #if SD_UIKIT
    //app退到后台后向UIApplication注册的后台任务标识,可以获得一些额外的下载时间
    @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
    #endif
    //对图片进行渐进式解码的解码器
    @property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
    

    下面来看一下SDWebImageDownloaderOperation的构造函数:

    - (nonnull instancetype)init {
        return [self initWithRequest:nil inSession:nil options:0];
    }
    
    - (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);
        }
        return self;
    }
    
    

    为SDWebImageDownloaderOperation添加回调block:

    - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                completed:(nullable SDWebImageDownloaderCompletedBlock)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;
    }
    

    progressBlock和completeBlock最终会以@{@"progress ":progressBlock,@"completed":completedBlock}的形式添加callbackBlocks数组。

    下面是通过字符串来获取所有的progressBlock或者所有的completedBlock:

    - (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
        LOCK(self.callbacksLock);
        //NSArray对KVC的拓展,所有元素都会执行valueForKey: 方法,并把结果添加到数组返回。如果valueForKey:返回nil,则会把NSNull添加进数组。
        NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
        UNLOCK(self.callbacksLock);
        // We need to remove [NSNull null] because there might not always be a progress block for each callback
        [callbacks removeObjectIdenticalTo:[NSNull null]];
        return [callbacks copy]; // strip mutability here
    }
    

    下面的代码是移除某一组回调的block:

    - (BOOL)cancel:(nullable id)token {
        BOOL shouldCancel = NO;
        LOCK(self.callbacksLock);
        [self.callbackBlocks removeObjectIdenticalTo:token];
        if (self.callbackBlocks.count == 0) {
            //当所有的回调都被移除,就没有下载的必要了。
            shouldCancel = YES;
        }
        UNLOCK(self.callbacksLock);
        if (shouldCancel) {
            [self cancel];
        }
        return shouldCancel;
    }
    

    下面来看看[self cancel]做了什么:

    - (void)cancel {
        @synchronized (self) {
            [self cancelInternal];
        }
    }
    
    - (void)cancelInternal {
        if (self.isFinished) return;
        //把operation置为取消状态
        [super cancel];
    
        if (self.dataTask) {
            //取消下载任务
            [self.dataTask cancel];
            __weak typeof(self) weakSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
            });
            if (self.isExecuting) self.executing = NO;
            if (!self.isFinished) self.finished = YES;
        }
    
        [self reset];
    }
    
    - (void)reset {
        LOCK(self.callbacksLock);
        //移除所有回调
        [self.callbackBlocks removeAllObjects];
        UNLOCK(self.callbacksLock);
        //释放请求任务
        self.dataTask = nil;
        if (self.ownedSession) {
            //释放内部生产的session
            [self.ownedSession invalidateAndCancel];
            self.ownedSession = nil;
        }
    }
    

    主要就是对取消之后资源的管理和状态的维护。

    下面来看看最主要的start方法,当SDWebImageDownloaderOperation添加到并发队列,就会调用start方法。

    - (void)start {
        @synchronized (self) {
            //如果operatin被取消,置位finished标志,并释放资源
            if (self.isCancelled) {
                self.finished = YES;
                [self reset];
                return;
            }
            
    #flag:注册后台任务
    #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)];
                //当app即将退到后台,想UIApplication注册一个后台执行的任务,以获取额外的操作时间
                self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                    __strong __typeof (wself) sself = wself;
                    if (sself) {
                        //当额外的时间后仍没有完成下载任务,则取消掉任务
                        [sself cancel];
                        [app endBackgroundTask:sself.backgroundTaskId];
                        sself.backgroundTaskId = UIBackgroundTaskInvalid;
                    }
                }];
            }
    #endif
            NSURLSession *session = self.unownedSession;
            if (!session) {
                //如果外部没有注入session,内部负责生成并管理一个session
                NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
                //SDWebImage默认的超时时间为15秒
                sessionConfig.timeoutIntervalForRequest = 15;
                session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                        delegate:self
                                                   delegateQueue:nil];
                self.ownedSession = session;
            }
            
            if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
                NSURLCache *URLCache = session.configuration.URLCache;
                if (!URLCache) {
                    URLCache = [NSURLCache sharedURLCache];
                }
                NSCachedURLResponse *cachedResponse;
                @synchronized (URLCache) {
                    //根据请求拿到缓存的响应
                    cachedResponse = [URLCache cachedResponseForRequest:self.request];
                }
                if (cachedResponse) {
                    //拿到NSUrlCache缓存的响应对应的数据
                    self.cachedData = cachedResponse.data;
                }
            }
            //实例化下载任务
            self.dataTask = [session dataTaskWithRequest:self.request];
            //设为运行中
            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];
            for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
                progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
            }
            __weak typeof(self) weakSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                //下载开始通知
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
            });
        } else {
            //如果下载任务实例化失败,则以错误的状态调用completedBlock回调
            [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
            [self done];
            return;
        }
    
    #从"flag:注册后台任务"到这行代码区间的代码,就是app退到后台向UIApplication注册的backgroundTask
    #if SD_UIKIT
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
            return;
        }
        if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
            UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
            [app endBackgroundTask:self.backgroundTaskId];
            self.backgroundTaskId = UIBackgroundTaskInvalid;
        }
    #endif
    }
    

    执行start后下载开始,接下来就是监听NSURLSessionDelegate 的代理方法来处理数据了。

    //接受到服务器响应数据
    - (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;
        self.response = response;
        NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
        BOOL valid = statusCode < 400;
        //'304 Not Modified'代表资源未改变,本地又没有NSUrlCache缓存的数据,当成非法请求看待
        if (statusCode == 304 && !self.cachedData) {
            valid = NO;
        }
        
        if (valid) {
            //如果响应有效,则回调处理所有progressBlock
            for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
                progressBlock(0, expected, self.request.URL);
            }
        } else {
            //取消后续下载
            disposition = NSURLSessionResponseCancel;
        }
        
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
        });
        
        if (completionHandler) {
            completionHandler(disposition);
        }
    }
    
    //接受到下载的数据包
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
        if (!self.imageData) {
            self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
        }
        //数据拼接
        [self.imageData appendData:data];
        //如果配置渐进式的下载,则对当前数据进行渐进式解码
        if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
            __block NSData *imageData = [self.imageData copy];
            const NSInteger totalSize = imageData.length;
            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, ^{
                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)}];
                    }
                    //每次解码调用completedBlock进行UIView的渲染
                    [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
                }
            });
        }
        //回调处理所有progressBlock
        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;
            __weak typeof(self) weakSelf = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                //发送下载停止通知
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
                if (!error) {
                    //如果下载没有错误,发送下载完成通知
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
                }
            });
        }
        
        //下载出错,以错误的状态调用completedBlock,同时把operation设置finished状态
        if (error) {
            [self callCompletionBlocksWithError:error];
            [self done];
        } else {
            if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
                __block NSData *imageData = [self.imageData copy];
                if (imageData) {
                    //这边为什么下载数据等于缓存数据要以nil调用completedBlock不是很懂,希望了解的人告知一二
                    if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
                        [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
                        [self done];
                    } else {
                        // 在解码队列上异步解码图片
                        dispatch_async(self.coderQueue, ^{
                            UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
                            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                            image = [self scaledImageForKey:key image:image];
                            
                            BOOL shouldDecode = YES;
                            // GIFs and WebPs不支持解码
                            if (image.images) {
                                shouldDecode = NO;
                            } else {
    #ifdef SD_WEBP
                                SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
                                if (imageFormat == SDImageFormatWebP) {
                                    shouldDecode = NO;
                                }
    #endif
                            }
                            
                            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) {
                                //当下载的图片大小为0,以错误的状态回调completedBlock
                                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                            } else {
                                //回调函数completedBlock处理下载的图片
                                [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
                            }
                            [self done];
                        });
                    }
                } else {
                    //当下载的图片为空,以错误的状态回调completedBlock
                    [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
                    [self done];
                }
            } else {
                [self done];
            }
        }
    }
    

    细心的你可能会发现,这些代理方法只是对ownedSession而言,那么对于unownedSession 如何监听呢?unownedSession代理方法由SDWebImageDownloader监听,然后一一转发到SDWebImageDownloaderOperation的代理方法中进行处理。这些我们在SDWebImageDownloader的解析中将会看到。

    以上关于解码的部分只是一笔略过,以后会专门写一篇。

    相关文章

      网友评论

          本文标题:SDWebImage解析之SDWebImageDownloade

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