一  SDWebImageManager的downloadImageWithURL的方法

  上一篇,我们刚开了个头,分析了一下开始加载图片之前如何取消其他正在下载的任务,接着,我们回到

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

   往下看。支持SDWebImageDelayPlaceholder,则优先显示placeholder。

if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}

  然后是最关键的步骤,使用SDWebImageManager单例调用downloadImageWithURL方法请求image,并且返回operation,保存到operationDictionary中(这个上一篇有介绍,这里的operation不是真正加载图片的operation)。在completedBlock中取得下载的image,更新UI。

深入看看SDWebImageManager的

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url

                                         options:(SDWebImageOptions)options

                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock

                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock

  在这里研究了多久,主要是补了一下NSOperation的知识,需要的同学也可以去补一补:http://www.cocoachina.com/game/20151201/14517.html

  我这里分析主要逻辑,其他一眼就能看明白的就不赘述了。这个方法里面做了几步优化:

  1. 维护一个URL黑名单failedURLs,下载失败后就加入黑名单,加入黑名单url再次加载的时候不会尝试去获取缓存或者重新下载,而是直接返回错误。
  2. 优先从缓存中获取,内存缓存,磁盘缓存都没有才去重新下载

  这里要另提一句,由于涉及多线程,这里使用了@synchronized关键字实现了锁,保证线程安全。关于iOS如何实现锁请参考我的文章:oc中实现锁

  最主要的是第二步,我们深入了解一下SDImageCache的queryDiskCacheForKey方法。

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
if (!doneBlock) {
return nil;
} if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
} // First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
} NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
} @autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
} dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
}); return operation;
}

可以看到,第一步是从内存中获取缓存

UIImage *image = [self imageFromMemoryCacheForKey:key];

如果内存没有,再从磁盘获取,获取成功后再保存在内存中

UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}

再回到SDWebImageManager的downloadImageWithURL的方法,从缓存中没有获取到image,则要重新下载,下载的时候调用SDWebImageDownloader的downloadImageWithURL方法。下载成功后,对image进行处理。优先处理options中包含 SDWebImageTransformAnimatedImage的情况,意思是在image下载成功后,保存到缓存之前,对图片进行处理,处理交给delegate去做。处理完之后,把处理过的image保存下来。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, ), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
} dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});

如果没有这种需求,就直接返回image并保存图片

if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
} dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});

大家可能对dispatch_main_sync_safe这个宏很好奇,他其实就是保证在主线程,看看他的定义

#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}

接下来我们看看SDWebImage单纯的异步下载图片是怎么实现的,让我们来分析分析SDWebImageDownloader的downloadImageWithURL方法。

这里又涉及到多线程开发的另一种形式GCD,不熟悉的赶紧去补课。

该方法的第一步是调用addProgressCallback:completedBlock:forURL:createCallback方法,将progressBlock,completedBlock以URL为key保存到self.URLCallbacks中。并且,如果self.URLCallbacks中该URL下没有block,才会执行SDWebImageDownloaderOperation去下载图片。

- (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, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
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] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL; if (first) {
createCallback();
}
});
}

这里有个GCD的方法大家可能比较陌生,dispatch_barrier_sync。它是同步插入一个任务,补课的同学移步这里:通过GCD中的dispatch_barrier_(a)sync加强对sync中所谓等待的理解

真正执行下载任务的是createCallback。我们再来研究研究createCallback干了什么。

在这里,就开始真正发送网络请求,进行图片下载,超时时间为15s。创建一个SDWebImageDownloaderOperation对象,加入self.downloadQueue中异步执行。需要注意的是,下载完成,或者该operation被cancel,就会将那些block从self.URLCallbacks中移除,[self.URLCallbacks removeObjectForKey:url];回忆一下addProgressCallback:completedBlock:forURL:createCallback方法,这样保证正在下载的过程中不会再起线程去多次下载。

第二篇到此结束,主要的代码已经分析完毕,第三篇会讲一些不常用的方法。咱们下篇见!

SDWebImage源码阅读-第二篇的更多相关文章

  1. SDWebImage源码阅读-第一篇

    一 题外话 之前写过一篇最新版SDWebImage的使用,也简单的介绍了一下原理.这两天正梳理自己的知识网络,觉得有必要再阅读一下源码,一是看具体实现,二是学习一下优秀开源代码的代码风格,比如接口设计 ...

  2. Flask源码阅读-第二篇(flask\__init__.py)

    源码: # -*- coding: utf-8 -*-""" flask ~~~~~ A microframework based on Werkzeug. It's e ...

  3. SDWebImage源码阅读-第三篇

    这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: com ...

  4. 【原】SDWebImage源码阅读(二)

    [原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还 ...

  5. 【原】SDWebImage源码阅读(四)

    [原]SDWebImage源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 SDWebImage中主要实现了NSURLConnectionDataDelega ...

  6. 【原】SDWebImage源码阅读(三)

    [原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们 ...

  7. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  8. 【原】SDWebImage源码阅读(一)

    [原]SDWebImage源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 一直没有系统地读过整套源码,就感觉像一直看零碎的知识点,没有系统读过一本专业经典书 ...

  9. SDWebImage 源码阅读分享

    SDWebImage 源码阅读分享 疑问列表 SDWebImage 整体框架图,主要的类包含哪些 SDWebImage 如何进行缓存管理,过期失效策略,缓存更新 SDWebImage 如何多线程处理的 ...

随机推荐

  1. 华为笔试——C++字符串四则运算的实现

    题目:字符串四则运算的实现 有字符串表示的一个四则运算表达式,要求计算出该表达式的正确数值.四则运算即:加减乘除"+-*/",另外该表达式中的数字只能是1位(数值范围0~9),运算 ...

  2. PAT甲题题解-1109. Group Photo (25)-(模拟拍照排队)

    题意:n个人,要拍成k行排队,每行 n/k人,多余的都在最后一排. 从第一排到最后一排个子是逐渐增高的,即后一排最低的个子要>=前一排的所有人 每排排列规则如下: 1.中间m/2+1为该排最高: ...

  3. LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”

    LINUX内核分析第四周学习总结--扒开系统调用的"三层皮" 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC ...

  4. 第二个spring冲刺第4天

    今天,我们团队参考了其他团队的四则运算的程序,发现很多地方可以学习. 1.别人的界面比较唯美,我们做的有点粗糙,所以这个必须要改善. 2.别人的具有较多的功能,比如计时器,我们要效仿. 3.还有难度选 ...

  5. OS X(10.10) python3.4 matplotlib的安装

    最近在用python做一些数据处理相关的工作,当然少不了matplotlib这个模块.之前在windows下分分钟安装成功,结果到了mac下死活编译不过去. 最后还是在stackoverflow上找到 ...

  6. Docker(十二)-Docker Registry镜像管理

    Registry删除镜像.垃圾回收 Docker仓库在2.1版本中支持了删除镜像的API,但这个删除操作只会删除镜像元数据,不会删除层数据.在2.4版本中对这一问题进行了解决,增加了一个垃圾回收命令, ...

  7. [财务知识] debt debit credit 的区别于联系

    https://blog.csdn.net/sjpljr/article/details/70169303 剑桥词典解释分别为: Debt [C or U ] n.something, especia ...

  8. 群里提到的IE设置问题 ---B/S 下页面刷新问题

    这里面四个选项的含义 下面是每个选项的作用和意义: 1. “每次访问此页时检查”选项表示浏览器每次访问一个页面时,不管浏览器是否缓存过此页面,都要向服务器发出访问请求.这种设置的优点是实时性很强,肯定 ...

  9. shareSDK.js web版的使用

    自定义将要分享的内容 <!--MOB SHARE BEGIN--> <div class="-mob-share-open">分享</div> ...

  10. 5G的作业- 云计算

    作业命题:5G对于保险行业的影响,技术层面和业务模式层面 一.5G网络的特点: 5G网络主要有三大特点,极高的速率 enhanced mobile broadband (eMBB),极大的容量 Mas ...