一  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. ABP中文文档及源码说明

    目录 ABP.ModuleZero(ABP自带的一套用户模块) 版本管理(Edition) 特性管理(Feature)

  2. 团队作业week9

    1. Bug bash ▪ How many bugs is found in your bug bash? 2. Write a blog to talk about your scenario t ...

  3. C# 7中函数多值返回_转自InfoQ

    本文要点 应遵循<.NET设计规范:.NET约定惯用法与模式>一书.和十年前第一版出版时一样,书中给出的原则在当前依然有指导意义. API设计是最重要的.设计不好的API会在极大地增加软件 ...

  4. 基于Winform框架DataGridView控件的SqlServer数据库查询展示功能的实现

    关键词:Winform.DataGridView.SqlServer 一个基于winform框架的C/S软件,主要实现对SqlServer数据库数据表的实时查询. 一.为DataGridView添加数 ...

  5. 微软必应词典UWP -2017春

    必应UWP调研,评测 软件平台:windows10 软件名称:微软必应词典 软件类型:UWP Bug Bug1 当在文本框中进行输入时,在谷歌拼音输入法状态下,无法使用Shift键切换到谷歌拼音的纯英 ...

  6. vue 有关框架

    iview-admin https://github.com/iview/iview-admin/ axios https://www.kancloud.cn/yunye/axios/234845

  7. 苹果手机input框上方有一条阴影线以及input框的placeholder颜色的设置

    今天做手机端的时候,用到input框来输入手机号码,但是在安卓手机上input的效果是正常的,在苹果手机上,input的上边框会变粗,有阴影 因为苹果手机的默认给input加上了阴影 给input加入 ...

  8. 【题解】Friends

    题目描述 有三个好朋友喜欢在一起玩游戏,A君写下一个字符串S,B君将其复制一遍得到T,C君在T的任意位置(包括首尾)插入一个字符得到U.现在你得到了U,请你找出S. 输入输出格式 输入格式 第一行,一 ...

  9. POJ 1182 食物链 (带权并查集)

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 78551   Accepted: 23406 Description ...

  10. Java线程总结---第一天

    线程和进程各自有什么区别和优劣: 进程是资源分配的最小单位,线程是程序执行的最小单位 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段.堆栈段和数据段,这种操 ...