SDWebImage源码解读

最近看了SdwebImage的源码,对大神的设计思想和架构模式有了初步的理解和感悟,跟大家分享一下自己的心得,如果有写的不对的地方,欢迎大家批评指正,共同进步。

一.此方法为UIimageView+webCache类的核心方法,UIIamgeView所有的设置图片的接口最终都会来到这里,此方法主要做了下面几件事:

 - (void)sd_setImageWithURL:(NSURL*)url placeholderImage:(UIImage*)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock

1.[self sd_cancelCurrentImageLoad];清理正在进行的下载操作,确保一个UIImageView对象同一时间只存在一个下载图片的操作。
2 objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);.动态的给UIImageView对象添加个成员变量缓存下载的URL。

3.  if (!(options & SDWebImageDelayPlaceholder)) {  
    dispatch_main_async_safe(^{
如果  options 不是 SDWebImageDelayPlaceholder 将占位图片显示出来
            self.image = placeholder;
        });
   }

默认情况下,当图片在加载中默认图片被加载。如果设置了 options = SDWebImageDelayPlaceholder 将延迟默认图片的显示,直到图片完成加载。
4.id operation = [SDWebImageManager.sharedManagerdownloadImageWithURL:urloptions:optionsprogress:progressBlockcompleted:^(UIImageimage,NSErrorerror,SDImageCacheTypecacheType,BOOLfinished,NSURL*imageURL)

创建下载管理器 SDWebImageManager调用起下载方法 并返回 SDWebImageCombinedOperation的对象,改对象是SDWebImage自定义的对象内部维护了一个NSOperation对象;

  1. [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    此方法为UIView+WebCacheOperation的方法,内部使用Runtime创建一个字典,改方法的调用是sd_setImageLoadOperation:forKey:添加到字典中,从而缓存operation;

二.接下来来到SDWebImageManager类调用如下方法,此方法主要对于下载并没有做太多的处理,主要是查找缓存,并进一步将下载的操作分配到SDWebImageDownloader中

- (id)downloadImageWithURL:(NSURL*)url

                                         options:(SDWebImageOptions)options

                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock

                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
  1. BOOLisFailedUrl =NO;
    @synchronized (self.failedURLs) {
    isFailedUrl = [self.failedURLscontainsObject:url];
    }
    这里是为了线程安全。
    如果URL不存在,或者(options!=SDWebImageRetryFailed 并且 这个URL无法下载图片)这里的SDWebImageRetryFailed默认情况下,当一个url下载失败,如果URL在黑名单中那么SDWebImage库不进行重试。这个标志会使黑名单失效。
  if(url.absoluteString.length==0|| (!(options &SDWebImageRetryFailed) && isFailedUrl)) {
       dispatch_main_sync_safe(^{//这是一个宏定义 内部逻辑为 如果当前线程是主现场那么就在当前线程做操作,如果不是主线程,就返回主线程。
        NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
          completedBlock(nil, error, SDImageCacheTypeNone,YES, url);//失败回调。
        });
        return operation;
    }
  @synchronized (self.runningOperations) {
        [self.runningOperationsaddObject:operation];
    }
将任务添加到数组中。manger会将所有的正在执行的Operation都放入这个数组,任务完成、失败和关闭都会从数组中移除,改数组后面的操作会用到,假如cancelAll被调用,就会让数组中的每一个Operation都调用cancel方法。

    NSString*key = [selfcacheKeyForURL:url];通过URL获取对用的Key。后面通过这个key去缓存中查缓存和本地中对用的图片。
operation.cacheOperation= [self.imageCachequeryDiskCacheForKey:keydone:^(UIImage*image,SDImageCacheTypecacheType) 改方法为查找缓存的方法,内容比较多,解析了我会分开逐句解读。

   if(operation.isCancelled) {

            @synchronized(self.runningOperations) {

                [self.runningOperationsremoveObject:operation];

            }

            return;

        }此方法是将被取消的任务,从数组中移除。

如果((缓存中不存在图片或者options = SDWebImageRefreshCached(更新缓存))&& (代理中未实现图片是否下载的方法||代理中图片是否应该下载方法返回值为YES)

   if((!image || options &SDWebImageRefreshCached) && (![self.delegaterespondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegateimageManager:selfshouldDownloadImageForURL:url]))

如果从缓存中获取了图片并且设置了SDWebImageRefreshCached来忽略缓存,则先把缓存的图片返回,然后继续后面的下载

    if(image && options &SDWebImageRefreshCached) {

                dispatch_main_sync_safe(^{

                    completedBlock(image,nil, cacheType,YES, url);

                });

            }

4.改方法的调用会来到SDWebImageDownloader的下载方法,此方法是下载操作的核心代码方法的实现稍后会解析

id subOperation = [self.imageDownloaderdownloadImageWithURL:urloptions:downloaderOptionsprogress:progressBlockcompleted:^(UIImage*downloadedImage,NSData*data,NSError*error,BOOLfinished){

                __strong __typeof(weakOperation) strongOperation = weakOperation;

                //如果图片下载结束以后,对应的图片加载操作已经取消。则什么处理都不做

if(!strongOperation || strongOperation.isCancelled) {

             }

                    if(  error.code != NSURLErrorNotConnectedToInternet

                        && error.code != NSURLErrorCancelled

                        && error.code != NSURLErrorTimedOut

                        && error.code != NSURLErrorInternationalRoamingOff

                        && error.code != NSURLErrorDataNotAllowed

                        && error.code != NSURLErrorCannotFindHost

                        && error.code != NSURLErrorCannotConnectToHost

                        && error.code != NSURLErrorNetworkConnectionLost) {

                        @synchronized (self.failedURLs) {

                            [self.failedURLs addObject:url];

                        }

                    }

                }

                else{//网络图片下载成功

                    //如果有重试失败下载的选项,则把url从failedURLS中移除

if((options & SDWebImageRetryFailed)) {

                        @synchronized (self.failedURLs) {

                            [self.failedURLs removeObject:url];

                        }

                    }

                    //是否需要保存到磁盘

BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    //如果设置了强制刷新缓存 && 缓存图片存在 && 下载图片不存在,那么直接返回,不执行回调Block

if(options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {

                        // Image refresh hit the NSURLCache cache, do not call the completion block                    }

                    //如果成功下载图片 && 图片是动态图片 && 实现了imageManager:transformDownloadedImage:withURL:代理方法,则进行图片的处理 

elseif(downloadedImage && (!downloadedImage.images || (options & 

SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {

                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

                            //获取transform以后的图片

UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            //存储transform以后的的图片if(transformedImage && finished) {

                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];

                                // 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];

                            }

                            //回调拼接                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];

                        });

                    } else {

                        //如果成功下载图片,直接缓存和回调

if(downloadedImage && finished) {

                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];

                        }

                        //回调拼接              

          [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];

                    }

                }

                //从正在加载的图片操作集合中移除当前操作

if (finished) {

                    [self safelyRemoveOperationFromRunning:strongOperation];

                }

            }];

三.改方法SDWebImageDownloader的核心方法,所有的图片下载都会分发到该类执行下载,该类为全局唯一的对象内部维护了一个下载的 downloadQueue,下载中的所有的operation所有的任务都会添加到这个队列中,URLCallbacks是一个字典,字典的key为下载的Url,vale为一个数组,数组中放的是个一个字典,字典中有两个键值对,对用的值为两个Block,完成的回调,和进度的回调

- (id)downloadImageWithURL:(NSURL*)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

1.将完成的回调和下载进度的block添加到URLCallbacks中缓存,如果是第一次下载并调用createCallback()

- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL*)url createCallback:(SDWebImageNoParamsBlock)createCallback {

    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.

    if(url ==nil) {

        if(completedBlock !=nil) {

            completedBlock(nil,nil,nil,NO);

        }

        return;

    }

    dispatch_barrier_sync(self.barrierQueue, ^{

        BOOLfirst =NO;

        if(!self.URLCallbacks[url]) {

            self.URLCallbacks[url] = [NSMutableArraynew];

            first =YES;

        }

        // Handle single download of simultaneous download request for the same URL

        NSMutableArray*callbacksForURL =self.URLCallbacks[url];

        NSMutableDictionary *callbacks = [NSMutableDictionary new];

        if(progressBlock) callbacks[kProgressCallbackKey] = [progressBlockcopy];

        if(completedBlock) callbacks[kCompletedCallbackKey] = [completedBlockcopy];

        [callbacksForURLaddObject:callbacks];

        self.URLCallbacks[url] = callbacksForURL;

        if(first) {

            createCallback();//,完成NSMutableURLRequest请求的创建、operation的创建、证书的认证、队列优先级的设置以及任务的执行顺序是按照队列的形式还是栈的形式

        }

    });

}

2.创建请求 SDWebImageDownloaderUseNSURLCache

//通常情况下request阻止使用NSURLCache. 这个选项会用默认策略使用NSURLCache

NSURLRequestUseProtocolCachePolicy

//默认缓存策略。具体工作:如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取。如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定,如果它指定内容必须重新生效的话。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取

NSURLRequestReloadIgnoringLocalCacheData

//URL应该加载源端数据,不使用本地缓存数据

HTTPShouldHandleCookies :具体可以参考iOS HTTPCookie基本使用 - 简书

HTTPShouldUsePipelining:通常默认情况下请求和响应是顺序的, 也就是说请求–>得到响应后,再请求. 如果将HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求. 这个会很大的提高网络请求的效率,但是也可能会出问题.因为客户端无法正确的匹配请求与响应, 所以这依赖于服务器必须保证,响应的顺序与客户端请求的顺序一致.如果服务器不能保证这一点, 那可能导致响应和请求混乱.

      NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];

        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);

        request.HTTPShouldUsePipelining = YES;

        if(wself.headersFilter) {

            request.allHTTPHeaderFields= wself.headersFilter(url, [wself.HTTPHeaderscopy]);

        }

        else{

            request.allHTTPHeaderFields= wself.HTTPHeaders;

        }

3.创建请求的Operation 调用改方法会来到SDWebImageDownloaderOperation,initWithRequest:方法并返回operation对象,该类继承NSOperation,并实现了NSURLSessionTaskDelegate, NSURLSessionDataDelegate,并且重写Start方法, [wself.downloadQueueaddOperation:operation];将任务添加到全局的downloadQueue,等于调用了Start方法。在start方法里面根据传入的self.session调用self.dataTask= [sessiondataTaskWithRequest:self.request];接着调用[self.dataTask resume];正式开启下载任务。由于SDWebImageDownloader遵循了<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>并实现了代理方法,self.session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];所以成功之后来到代理方法内,这里作者在SDWebImageDownloader的代理手动调用SDWebImageDownloaderOperation代理方法将请求的数据分发到SDWebImageDownloaderOperation的代理中然后调用成功的回调重新成功的或者失败的数据返回过来,下面的block内部的就是对返回数据的处理,并包括完成后的任务的清除。整个加载过程到这里就结束,关于图片解压缩的流程,后面我会单独写一篇文细说。

 operation = [[wself.operationClassalloc]initWithRequest:request

                                                        inSession:self.session

                                                          options:options

                                                         progress:^(NSIntegerreceivedSize,NSIntegerexpectedSize) {

                                                             SDWebImageDownloader*sself = wself;

                                                             if(!sself)return;

                                                             __blockNSArray*callbacksForURL;

                                                             dispatch_sync(sself.barrierQueue, ^{

                                                                 callbacksForURL = [sself.URLCallbacks[url]copy];

                                                             });

                                                             for(NSDictionary*callbacksincallbacksForURL) {

                                                                 dispatch_async(dispatch_get_main_queue(), ^{

                                                                     SDWebImageDownloaderProgressBlockcallback = callbacks[kProgressCallbackKey];

                                                                     if(callback) callback(receivedSize, expectedSize);

                                                                 });

                                                             }

                                                         }

                                                        completed:^(UIImage*image,NSData*data,NSError*error,BOOLfinished) {

                                                            SDWebImageDownloader*sself = wself;

                                                            if(!sself)return;

                                                            __blockNSArray*callbacksForURL;

                                                            dispatch_barrier_sync(sself.barrierQueue, ^{

                                                                callbacksForURL = [sself.URLCallbacks[url]copy];

                                                                if(finished) {

                                                                    [sself.URLCallbacksremoveObjectForKey:url];

                                                                }

                                                            });

                                                            for(NSDictionary*callbacksincallbacksForURL) {

                                                                SDWebImageDownloaderCompletedBlockcallback = callbacks[kCompletedCallbackKey];

                                                                if(callback) callback(image, data, error, finished);

                                                            }

                                                        }

                                                        cancelled:^{

                                                            SDWebImageDownloader*sself = wself;

                                                            if(!sself)return;

                                                            dispatch_barrier_async(sself.barrierQueue, ^{

                                                                [sself.URLCallbacksremoveObjectForKey:url];

                                                            });

                                                        }];
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容