一  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方法,这样保证正在下载的过程中不会再起线程去多次下载。

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

最新文章

  1. 10 款最好的 Python IDE
  2. c# long转 datetime
  3. 选中没有选中的复选框,匹配含有某个字符串的正则,json取值的两种方法,把变量定义在外面跟里面的区别
  4. RHEL7搭建DHCP
  5. Spring MVC 的汉字乱码问题
  6. MVC Action,Service中筛选int 和list&lt;int&gt;
  7. iOS8上放大缩小的动画
  8. rsyslog同步history日志(转载)
  9. poj1920 Towers of Hanoi
  10. 百度地图API地点搜索-获取经纬度
  11. new del 问题
  12. centos7.2部署最新ELK 5.3
  13. node.js上除了Express还有哪些好用的web开发框架
  14. 《Pro Android Graphics》读书笔记之第六节
  15. Vue-admin工作整理(十六):Ajax-axios进行请求封装+拦截器
  16. Windows操作系统发展历程
  17. 有哪些知名的公司在用Python
  18. 12C - PDB archive file
  19. Rabbit RPC 代码阅读(一)
  20. &lt;!--[if IE]&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;matrix/js/html5.js&quot;&gt;&lt;/script&gt;&lt;![endif]--&gt;代码解释

热门文章

  1. SCRUM 12.09 软件工程第二周计划
  2. 1.AKATSUKI
  3. 20135202闫佳歆--week2 一个简单的时间片轮转多道程序内核代码及分析
  4. Alpha版使用说明
  5. 每日scrum(6)
  6. 腾讯云申请的64位ubuntu服务器配置php环境
  7. 如何用Qt自动拷贝exe依赖的dll
  8. Linux命令(二十二) 改变文件权限 chomd
  9. Django-项目配置
  10. 一本通1639Biorhythms