美文网首页
SDWebImage源码解析

SDWebImage源码解析

作者: CerasusLand | 来源:发表于2017-07-21 13:55 被阅读16次

    概述

    SDWebImage是一个强大的图片下载框架,利用异步加载和内存+磁盘两级缓存处理,高效优雅的解决了图片下载的问题.

    This library provides an async image downloader with cache support. For convenience, we added categories for UI elements like UIImageView, UIButton, MKAnnotationView.
    引用官方文档的一句话:异步下载,支持缓存,并且利用分类的方式为UIImageView,UIButton,MKAnnotationView等控件添加了方便的下载方法,简言之就是,强大且好用.

    一个常见的下载方法的调用是这样的:
    [self.imageview sd_setImageWithURL:[NSURL URLWithString:@"http://www.pp3.cn/uploads/201609/2016092105.jpg"] placeholderImage:[UIImage imageNamed:@"dragon.jpg"] options:SDWebImageCacheMemoryOnly progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            NSLog(@"%ld",expectedSize);
            NSLog(@"%ld",receivedSize);
        } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
            NSLog(@"Finished!");
        }];
    

    核心功能类:

    UIImageView+WebCache**,UIButton+WebCache,MKAnnotationView+WebCache

    作为直接面对用户调用的接口,框架通过给控件添加分类的形式方便的赋予了下载图片的功能.而这一切得益于后台几个类的相互配合;

    SDWebImageManager

    持有SDWebCache和SDWebImageDownloader对象,从而决定一个图片是从已有的缓存中取到,还是需要从网络下载;

    SDWebImageDownloader

    维护一个操作队列,并且管理下载过程中的进度和下载好的回调;

    SDImageCache

    负责为缓存开辟空间包括内存缓存和磁盘缓存,写入缓存,管理缓存,读出缓存,清理缓存等工作;

    SDWebImageDownloaderOperation

    这是一个NSOperation的子类,代表一个下载操作,以回调block或者代理的方式取到下载的图片数据或者错误信息.

    按照软件工程的一般套路,我们剖析代码的思路也是:从整体到局部,从上层到下层,按功能线回溯,各行其是,分而治之

    UI层

    UI层通过给UIImageView,UIButton,MKAnnotationView添加分类,拓展了一个下载图片的方法,框架针对不同的情形,给出了多种方法可供调用,但是本质上都是调用了下面这个方法:

    UIView+WebCacheOperation

    考虑到很多UIView的子控件都需要持有当前的operation(s),作者添加了一个UIView的分类,用来存储加载视图上面的operation(s),同时添加了一系列方法来维护这些operation(s).

    //设置图片下载操作
    - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
    //取消图片下载操作
    - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
    ///移除图片操作
    - (void)sd_removeImageLoadOperationWithKey:(NSString *)key;
    

    但是为什么要这么做呢?举例来说,tableViewCell上面有很多UIImageView,快速滑动的时候,其实很多imageView快速出来,又快速消失了,这个时候把消失了的视图上面的下载操作取消掉,是更好的选择.

    UIImageView+WebCache

    这个分类里面,提供了很多便利化的方法可供调用,但是最终都会调到下面这个方法的实现,我在这个方法里面添加了注释:

    - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    //url: 图片对应的URL
    //placeholder: 预显示的占位图片,从项目的Bundle里面加载;
    //option: SDWebImageOptions的枚举,定义了一系列用户的定制项;
    //progressBlock: SDWebImageDownloaderProgressBlock下载进度的回调block,用户可以在这里取到要下载的文件大小,和已下载的文件大小,用来跟踪下载进度或者定制进度条;
    //completedBlock: 下载完成的回调block,包含image,error,url,以及完成与否的bool值.
    
        [self sd_cancelCurrentImageLoad];//取消对应当前控件对应的下载任务
        
        objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        //将当前UIImageView对象与其URL进行关联:  UIImageView <--> Url
        if (!(options & SDWebImageDelayPlaceholder)) {//
            dispatch_main_async_safe(^{
                self.image = placeholder;//添加占位图片
            });
        }
        
        if (url) {
            // check if activityView is enabled or not
            // 添加下载指示器
            if ([self showActivityIndicatorView]) {
                [self addActivityIndicator];
            }
            
            //调用SDWebImageManager单例的下载方法,返回一个遵循了SDWebImageOperation协议的operation对象.
            __weak __typeof(self)wself = self;
            id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                [wself removeActivityIndicator];//完成移除指示器
                if (!wself) return;
                dispatch_main_sync_safe(^{
                    if (!wself) return;
                    if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                    {
                        completedBlock(image, error, cacheType, url);
                        return;
                    }
                    else if (image) {
                        wself.image = image;
                        [wself setNeedsLayout];
                    } else {
                        if ((options & SDWebImageDelayPlaceholder)) {
                            wself.image = placeholder;
                            [wself setNeedsLayout];
                        }
                    }
                    if (completedBlock && finished) {
                        completedBlock(image, error, cacheType, url);
                    }
                });
            }];
            [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];//添加当前控件的operation
        } else {//当url为空的时候
            dispatch_main_async_safe(^{
                [self removeActivityIndicator];//移除指示器
                if (completedBlock) {
                    NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                    completedBlock(nil, error, SDImageCacheTypeNone, url);//调用完成的block抛出异常信息
                }
            });
        }
    }
    

    其他的控件上的下载操作和这个类似,这里就不一一列举了,方法调到这里就会离开UI层而来到控制层.

    Tips:
    1.OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    这是利用Runtime来给分类添加属性的方法,object是要关联属性的对象,key是一个常量字符,value是要关联的属性,policy是关联策略.

    2.dispatch_main_async_safe(block)
    这是自定义的宏,用来保证block只在主线程异步执行.

    控制层

    控制层利用一个SDWebImageManager来协调图片是从网路下载还是从缓存中获取,并且决定是否刷新缓存,SDWebImageManager持有SDImageCache和SDWebImageDownloader对象.

    SDWebImageManager

    职能:

    1. 发出下载指令,并取得下载的操作
    2. 指定Url转换成缓存用的唯一key
    3. 发出缓存指令,给定key
    4. 检查缓存是否存在,包括在缓存和磁盘中
    5. 取消所有的下载操作

    实现:

    定义SDWebImageOptions枚举

    typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
        SDWebImageRetryFailed = 1 << 0,//1 是否重试
        SDWebImageLowPriority = 1 << 1,//2 当前若有UI操作的时候,延迟下载
        SDWebImageCacheMemoryOnly = 1 << 2,//4 只允许内存缓存\不允许磁盘缓存
        SDWebImageProgressiveDownload = 1 << 3,//8 采用渐进式加载\默认情况下是图片下载完成之后才渲染
        SDWebImageRefreshCached = 1 << 4,//16 刷新缓存
        SDWebImageContinueInBackground = 1 << 5,//32 开启后台下载功能
        SDWebImageHandleCookies = 1 << 6,//64 为请求保存Cookies
        SDWebImageAllowInvalidSSLCertificates = 1 << 7,//128 允许无效的SSL证书
        SDWebImageHighPriority = 1 << 8,//256 把下载任务移到任务队列的front位置,保证优先级最高
        SDWebImageDelayPlaceholder = 1 << 9,//512 延迟加载占位图片
        SDWebImageTransformAnimatedImage = 1 << 10,//1024 使用动画图片 
        SDWebImageAvoidAutoSetImage = 1 << 11//2048 避免自动设置图片
    };
    

    Manager通过SDWebImageDownloader对象下达加载图片的指令,都在下面这个方法里面:

    - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                             options:(SDWebImageOptions)options
                                            progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
    

    1.判断url是否有效,无效则把url指定为nil,如果是以前就失败了的url并且options为SDWebImageRetryFailed,则调用completedBlock给出错误提示,方法返回当前操作,否则进行步骤2;
    2.往runningOperations增加operation,并把url转换成标识图片的key;
    3.调用imageCache的- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock方法,从内存和磁盘里查询缓存,如果缓存存在调用completedBlock,返回查找到的图片,否则进行步骤4;
    4.如果没有命中缓存,或者指定需要刷新缓存,则开始启动imageDownloader的下载程序- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock,把返回的operation添加到下载队列中,这里并没有提供磨人的progressBlock的实现,只是在completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)里面处理下载结束的状态,错误信息,返回下载好的图片并把图片在存到缓存中.

    - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(nullable SDInternalCompletionBlock)completedBlock {
        //completedBlock为空时候的异常处理
        NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
        
        //对url的纠错处理,如果传入的是字符串类型,则强制转换成NSURL
        if ([url isKindOfClass:NSString.class]) {
            url = [NSURL URLWithString:(NSString *)url];
        }
        
        //如果是其他不能强制转换成NSURL的类型,或者是NSNull,则直接置为nil,后面碰到nil就会走处理nil的部分
        if (![url isKindOfClass:NSURL.class]) {
            url = nil;
        }
    
        __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
        __weak SDWebImageCombinedOperation *weakOperation = operation;
        
        //failedURLs用来保存失败了的urls,加互斥锁用保证多线程安全
        BOOL isFailedUrl = NO;
        if (url) {
            @synchronized (self.failedURLs) {
                isFailedUrl = [self.failedURLs containsObject:url];
            }
        }
        
        //如果给定的url不满足条件,开始往失败的分支里面走,其实就是调用completedBlock给定空的image
        if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
            [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
            return operation;
        }
        
        //如果能走到这里来,说明操作本次操作可以执行,就添加到正在执行的operations
        //同样加互斥锁保证线程安全
        @synchronized (self.runningOperations) {
            [self.runningOperations addObject:operation];
        }
        
        //通过url转换成key,默认情况是直接返回url.absoluteString,如果用户有给定对url的筛选规则,则考虑自定义的规则
        NSString *key = [self cacheKeyForURL:url];
        
        //调用SDImageCache的查询缓存的方法,通过key来查找缓存
        operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
            //如果操作中途被取消,则用线程安全的方式取消正在进行中的operation,然后方法返回
            if (operation.isCancelled) {
                [self safelyRemoveOperationFromRunning:operation];
                return;
            }
            
            //如果用户要刷新缓存 && 没有自己实现下载图片的方法,那就要走框架给定的下载方法
            if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
                if (cachedImage && options & SDWebImageRefreshCached) {
                    
                    //如果命中缓存图片,但是用户强制刷新缓存,那就先提供缓存图片,同时再去服务端下载一边,当图片的内容改变了,但是url没有变的时候适用
                    [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
                }
                
                //如果没有图片或者强制刷新,则开始从服务器加载图片
                SDWebImageDownloaderOptions downloaderOptions = 0;
                //...
                
                //开启创建下载任务
                SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (!strongOperation || strongOperation.isCancelled) {
                        //如果操作被取消,那就什么都不做
                    } else if (error) {
                        //下载出现错误的处理
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                        //...
                    }
                    else {
                        if ((options & SDWebImageRetryFailed)) {
                            @synchronized (self.failedURLs) {
                                [self.failedURLs removeObject:url];
                            }
                        }
                        
                        BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
    
                        if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                            // Image refresh hit the NSURLCache cache, do not call the completion block
                        } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                //对动画图片(GIF)的处理
                                UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
    
                                if (transformedImage && finished) {
                                    BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                    //调用SDWebImageCache的存储方法,开始往内存或者磁盘里面缓存图片内容,key是上文中给定的url,在SDWebImageCache类的内部还会对key做进一步的处理
                                    // pass nil if the image was transformed, so we can recalculate the data from the image
                                    [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                                }
                                //调用completetionBlock返回结果
                                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                            });
                        } else {
                            if (downloadedImage && finished) {
                                //调用SDWebImageCache的存储方法,开始往内存或者磁盘里面缓存图片内容,key是上文中给定的url,在SDWebImageCache类的内部还会对key做进一步的处理
                                [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            //调用completetionBlock返回结果
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        }
                    }
    
                    if (finished) {
                        //安全移除完成了的操作
                        [self safelyRemoveOperationFromRunning:strongOperation];
                    }
                }];
                operation.cancelBlock = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            } else if (cachedImage) {
                //命中缓存并且不刷新缓存则,直接回调completetionBlock,返回缓存
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
                //安全移除完成了的操作
                [self safelyRemoveOperationFromRunning:operation];
            } else {
                //既没有缓存,也不允许下载,则返回错误
                // Image not in cache and download disallowed by delegate
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
                [self safelyRemoveOperationFromRunning:operation];
            }
        }];
        
        //返回当前操作对象
        return operation;
    }
    

    Tips:
    1.类型纠错
    有时候我们误把NSString类型当做NSURL,或者传递的是非URL得类型,框架会帮我们做类型纠错,把字符串类型转换成NSURL,如果是非URL类型,就转换成nil,以免造成后面的误操作.

    2.线程加锁

    BOOL isFailedUrl = NO;
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];//
        }
    

    框架定义了一个NSMutableSet类型的failedURLs,用来存储那些失败了的urls,但是考虑到多个线程要操作这个结构,所以用@synchronized加了个互斥锁,以免造成线程安全问题.

    //添加当前下载操作
    @synchronized (self.runningOperations) {
            [self.runningOperations addObject:operation];//
        }
    //移除当前下载操作
    @synchronized (self.runningOperations) {
                            if (strongOperation) {
                                [self.runningOperations removeObject:strongOperation];
                            }
                        }
    //移除所有当前下载操作
    @synchronized (self.runningOperations) {
            NSArray *copiedOperations = [self.runningOperations copy];
            [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
            [self.runningOperations removeObjectsInArray:copiedOperations];
        }
    //判断有没有正在进行的下载操作
    - (BOOL)isRunning {
        BOOL isRunning = NO;
        @synchronized(self.runningOperations) {
            isRunning = (self.runningOperations.count > 0);
        }
        return isRunning;
    }
    

    runningOperations是一个可变数组,可以用以对当前的operations进行操作,同样需要保证在多线程环境下的安全性.

    SDWebImageDownloader

    这个类其实已经很接近下载操作了,但是还是一个中间层.

    职能:
    1.配置NSURLSession的默认配置(包括请求头信息,超时时间,urlCredential等),生成NSURLSession对象;
    2.维护一个下载队列downloadQueue,可以往队列里面添加任务,删除任务,或者调配任务的执行顺序;
    3.根据session和request创建operation对象,并加到操作队列里面,指定下载任务和操作的对应关系;
    4.把task的返回数据的代理方法,指向对应的operation的代理方法里面.

    实现:

    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
        __weak SDWebImageDownloader *wself = self;
        
        return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
            __strong __typeof (wself) sself = wself;
            
            //设置请求的时间间隔
            NSTimeInterval timeoutInterval = sself.downloadTimeout;
            if (timeoutInterval == 0.0) {
                timeoutInterval = 15.0;
            }
    
            //设置缓存策略,创建urlRequest
            NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
            request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
            request.HTTPShouldUsePipelining = YES;
            
            //设置请求头信息
            if (sself.headersFilter) {
                request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
            } else {
                request.allHTTPHeaderFields = sself.HTTPHeaders;
            }
            
            //创建下载的操作对象operation,关联了request和session
            SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
            
            //是否解压缩图片
            operation.shouldDecompressImages = sself.shouldDecompressImages;
            
            //设置请求的身份验证信息
            if (sself.urlCredential) {
                operation.credential = sself.urlCredential;
            } else if (sself.username && sself.password) {
                operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
            }
            
            //设置队列优先级
            if (options & SDWebImageDownloaderHighPriority) {
                operation.queuePriority = NSOperationQueuePriorityHigh;
            } else if (options & SDWebImageDownloaderLowPriority) {
                operation.queuePriority = NSOperationQueuePriorityLow;
            }
            
            //将操作加入到队列中
            [sself.downloadQueue addOperation:operation];
            
            //设置队列中任务的执行顺序,默认是FIFO
            if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
                // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
                [sself.lastAddedOperation addDependency:operation];
                sself.lastAddedOperation = operation;
            }
            
            //返回operation
            return operation;
        }];
    }
    

    另外在请求接收数据的代理方法里面,会根据task对应的dataOperation请用指定的代理方法,这样就实现了downloader这个类只是起协调作用,真正接受处理数据还是在operation里面,下面会介绍:

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
        // Identify the operation that runs this task and pass it the delegate method
        SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
    
        [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
    }
    

    SDWebImageDownloaderOperation

    正在处理下载任务和接受数据的地方.

    职能:
    1.由于继承自NSOperation所以,本类有一系列对operation的操作,例如,start,cancel,done,reset,finished;
    2.持有request和session,如果为空则在start的方法里面创建请求和发起dataTask;
    3.在代理方法里面接受和处理数据.

    实现:

    - (void)start {
        //取消和重置当前操作
        @synchronized (self) {
            if (self.isCancelled) {
                self.finished = YES;
                [self reset];
                return;
            }
            
            //TARGET_OS_IOS || TARGET_OS_TV 如果是iOS平台或者TV平台,则会在应用切到后台的时候开启后台下载任务
    #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)];
                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 (!self.unownedSession) {
                NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
                sessionConfig.timeoutIntervalForRequest = 15;
                
                //为下载任务创建session,当delegateQueue为nil的时候,会默认在一个串行队列中执行代理方法
                self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                                  delegate:self
                                                             delegateQueue:nil];
                session = self.ownedSession;
            }
            
            self.dataTask = [session dataTaskWithRequest:self.request];
            self.executing = YES;
        }
        
        [self.dataTask resume];
        
        if (self.dataTask) {
            //处理任务进度的回调
            for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
                progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
            });
        } else {
            [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
        }
        
    #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方法是发起下载任务的地方,并且考虑到了后台任务的情况,对于iOS平台和TV平台,当应用切换到后台的时候,会以一个指定的id继续下载,在任务开始的收会处理任务进度的回调和发送一个SDWebImageDownloadStartNotification的通知.

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
        
        //'304 Not Modified' 表明服务端的内容没有做修改,这种情形会单独考虑
        if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
            NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
            self.expectedSize = expected;
            //获取到将要接受的数据的二进制形式的长度,调用progressBlock处理下载进度
            for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
                progressBlock(0, expected, self.request.URL);
            }
            
            self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
            self.response = response;
            //在主线程发出一个SDWebImageDownloadReceiveResponseNotification
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
            });
        } else {
            NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
            //如果响应状态码为304服务端的内容没有做修改,这时候不用去接受图片数据,只需要取消当前操作并返回缓存中的图片就好了
            //这样能节省用户流量,提高效率
            if (code == 304) {
                [self cancelInternal];
            } else {
                [self.dataTask cancel];
            }
            //在主线程发出SDWebImageDownloadStopNotification的通知
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
            });
            
            [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
            
            [self done];
        }
        
        if (completionHandler) {
            completionHandler(NSURLSessionResponseAllow);
        }
    }
    

    由于框架用的直接是系统的UrlSession加载数据,所以在接收到response的时候需要声明接收数据的NSData对象,对于304的隔离处理能有效的帮助用户节省流量和提高图片显示效率.

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    

    这里主要是将接收到的图片二进制数据拼接到imageData里面去,并处理下载的进度情况,值得注意的是如果用户指定了以边加载边显示的方式渲染图片的话,这里做了很多画图的工作,这里就不详述图片的绘制过程,有兴趣的同学请跳

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    

    下载完成之后,对图片数据进行尺寸的调整和压缩,并且调用回调block,至此从服务端下载图片的流程就能走通了.

    SDImageCache

    这里真正执行图片缓存的地方,涉及到缓存的增,删,查找的诸多操作,还有很多异步的文件操作..

    耳熟能详的枚举:

    typedef NS_ENUM(NSInteger, SDImageCacheType) {
        SDImageCacheTypeNone,//不缓存
        SDImageCacheTypeDisk,//磁盘缓存
        SDImageCacheTypeMemory//内存缓存
    };
    

    根据manager给定的key(默认是图片的url)生成存储文件的文件名,这里就是采用MD5摘要算法,生成唯一文件名.

    - (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
        const char *str = key.UTF8String;
        if (str == NULL) {
            str = "";
        }
        unsigned char r[CC_MD5_DIGEST_LENGTH];
        CC_MD5(str, (CC_LONG)strlen(str), r);
        NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                              r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                              r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
    
        return filename;
    }
    

    在高版本的SD中,作者把判断图片文件类型的方法单独到了一个NSData+ImageContentType的类里面,用到的判断依据是:拿到一张图片十六进制数据的第一个字节进行判断,然后再根据不同的结果返回图片的真实类型。只要是同一种类型的图片,它十六进制数据的第一个字节都是相同的,JPEG图片十六进制数据的第一个字节是0xFF,PNG图片十六进制数据的第一个字节是0x89,GIF图片十六进制数据的第一个字节是0x47.

    核心方法:查询缓存

    - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {   
        if (!key) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return nil;
        }       
        //查看内存缓存
       UIImage *image = [self imageFromMemoryCacheForKey:key];    
        if (image) {    
            NSData *diskData = nil;        
            if ([image isGIF]) {
                diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            }        
            if (doneBlock) {
                doneBlock(image, diskData, SDImageCacheTypeMemory);
            }        
            return nil;
        }
        //查看磁盘缓存
        NSOperation *operation = [NSOperation new];    
        dispatch_async(self.ioQueue, ^{        
            if (operation.isCancelled) {
                // 在用之前就判断operation是否被取消了,作者考虑的非常严谨
                return;
            }
            @autoreleasepool {            
                NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
                UIImage *diskImage = [self diskImageForKey:key];            
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = SDCacheCostForImage(diskImage);                
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
                if (doneBlock) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                    });
                }
            }
        });
        return operation;
    }
    

    结语

    致此已经分析了SDWebImage框架的核心内容,可见框架的博大,还没有涉及到的以后再补充,源代码是程序员智慧的结晶,向大神致敬.

    相关文章

      网友评论

          本文标题:SDWebImage源码解析

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