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