一  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. PAT甲题题解-1039. Course List for Student (25)-建立映射+vector

    博主欢迎转载,但请给出本文链接,我尊重你,你尊重我,谢谢~http://www.cnblogs.com/chenxiwenruo/p/6789157.html特别不喜欢那些随便转载别人的原创文章又不给 ...

  2. Notes of Daily Scrum Meeting(12.20)

    今天是周六,大家空余的时间还是挺多的,也都主动完成了当天工作,最后由于我的失误,在晚上12点 之前没有把进度签入进TFS里面,所以周六的燃尽图是错误的,我把进度加进周日,总的进度会在周日的燃尽 图里面 ...

  3. 动态绑定,多态,static

    1:下面程序的输出结果为: class base2{ static{ System.out.println("static base2"); } } public class te ...

  4. Beta阶段冲刺-3

    一. 每日会议 1. 照片 2. 昨日完成工作 3. 今日完成工作 4. 工作中遇到的困难 杨晨露:组内图表部分遇到了问题,他们的问题一出来,我就头疼了......因为要调整计划时间,所以我觉得我的困 ...

  5. 项目UML设计(团队)

    团队 队名 boy next door 队员 林晗 124(组长) 微博链接 显东 114 微博链接 基智 505 微博链接 松雄 126 微博链接 团队分工 分工表 任务 林晗 显东 基智 松雄 前 ...

  6. MongoDB ,cursor not found异常

    查询mongoDB集合数据更新,数据有400w多.我一次用cursor(游标)取1w,处理更新.程序在某段时间运行中遍历游标时发生异常! DBCursor cursor = tabColl.find( ...

  7. 蜗牛慢慢爬 LeetCode 22. Generate Parentheses [Difficulty: Medium]

    题目 Given n pairs of parentheses, write a function to generate all combinations of well-formed parent ...

  8. [转帖] 一文看懂:"边缘计算"究竟是什么?为何潜力无限?

    一文看懂:"边缘计算"究竟是什么?为何潜力无限? 转载cnbeta   云计算 雾计算 边缘计算...   知名创投调研机构CB Insights撰文详述了边缘计算的发展和应用前景 ...

  9. 微信小游戏 4M升8M分包加载

    一.微信分包加载 微信分包加载教程 嘛,因为原来的4M太小了,满足不了小游戏内容的需求,现在提升到了8M.这8M可以分包加载,而不需要一次性加载8M. 如果是老版本,则分包加载不起作用,会一次加载8M ...

  10. 【题解】 [HNOI2015]菜肴制作 (拓扑排序)

    题目描述 知名美食家小 A被邀请至ATM 大酒店,为其品评菜肴. ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高到低给予1到N的顺序编号,预估质量最高的菜肴编号为1. 由于菜肴 ...