本文转载请注明出处 —— polobymulberry-博客园html
一直没有系统地读过整套源码,就感受像一直看零碎的知识点,没有系统读过一本专业经典书籍同样,会有点发虚,感受知识体系不健全!废话少说,此次我决定好好阅读下SDWebImage的源码,个人阅读方式,是带着问题去阅读源码,而后强迫本身写博客。git
既然是要带着问题读,那么第一个问题就来了,SDWebImage是作什么的?SDWebImage是一个开源的代码库,咱们能够在github上找到它 —> Github传送门。github
Github上是这样介绍它的:web
This library provides a category for UIImageView with support for remote images coming from the web.缓存
因此咱们大概知道SDWebImage就是一个库。这个库本质是UIImageView的category。为啥要作这个category呢?是为了从服务器端远程获取图片到UIImageView上显示。固然,看完代码后,就知道SDWebImage提供的功能远不止说的这么简单。服务器
github上也给了一些例子,咱们看一下最经常使用的一个例子:网络
1 #import <SDWebImage/UIImageView+WebCache.h> 2 3 ... 4 5 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 6 static NSString *MyIdentifier = @"MyIdentifier"; 7 8 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; 9 if (cell == nil) { 10 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 11 reuseIdentifier:MyIdentifier] autorelease]; 12 } 13 14 // Here we use the new provided sd_setImageWithURL: method to load the web image 15 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] 16 placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; 17 18 cell.textLabel.text = @"My Text"; 19 return cell; 20 }
这确实是一个很常见的需求,就是在一个tableView上,每个cell都须要显示网络端获取的image。好比咱们经常使用的新浪微博、网易新闻、知乎日报等等,都会用到。app
这里最关键的一行代码就是:dom
1 // Here we use the new provided sd_setImageWithURL: method to load the web image 2 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] 3 placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
看到这里,我不由自主地要赞叹两句,这个接口设计的真的很棒!你想一想,我要从网络端获取图片,并显示到UIImageView上,其实我只要给你一个图片的url就ok啦,另外当前图片若是还未获取到,怎么办?弄个placeholderImage呗(当网络端图片还未加载完成,做为一个替代的图片,好比一些app若是网络很差的话,文章对应图片加载不出来,就会显示带有“暂无图片”的图片)。async
其中的图片如何获取,如何缓存等等都屏蔽了。甚至没有暴露从网络端获取到的是什么图片,固然后面咱们会提到SDWebImage中有其余的借口会暴露返回的图片image,容许在image上操做后再赋值给imageView。
细想下其中的过程,我大致有一个简单的实现概念(先本身想一想怎么实现,而后对照实际源码,这样才能看到本身不足):
而后发出网络请求,获取图片image
若是图片获取成功,赋值给UIImageView
带着我这简陋的想法,我模仿SDWebImage写了以下代码:
首先我建立了一个UIImageView的category —— UIImageView+Extension.h
主要是模仿SDWebImage的 sd_setImageWithURL:placeholderImage:函数写了一个pjx_setImageWithURL:placeholderImage:
- UIImageView+Extension.h
1 #import <UIKit/UIKit.h> 2 3 @interface UIImageView (Extension) 4 5 - (void)pjx_setImageWithURL:(NSURL *)imageUrl placeholderImage:(UIImage *)placeholderImage; 6 7 @end
- UIImageView+Extension.m
1 #import "UIImageView+Extension.h" 2 3 @implementation UIImageView (Extension) 4 5 - (void)pjx_setImageWithURL:(NSURL *)imageUrl placeholderImage:(UIImage *)placeholderImage 6 { 7 // 1.先将UIImageView的image设为placeholderImage 8 self.image = placeholderImage; 9 10 // 2.而后发出网络请求,获取图片image 11 NSData *imageData = [NSData dataWithContentsOfURL:imageUrl]; 12 UIImage *image = [UIImage imageWithData:imageData]; 13 14 // 3.若是图片获取成功,赋值给UIImageView 15 if (image) { 16 self.image = image; 17 } 18 } 19 20 @end
ViewController调用代码:
1 #import "ViewController.h" 2 #import "UIImageView+Extension.h" 3 4 @interface ViewController () 5 6 @property (weak, nonatomic) IBOutlet UIImageView *imageView; 7 8 @end 9 10 @implementation ViewController 11 12 #pragma mark - life cycle 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 16 NSString *baiduLogoString = @"https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png"; 17 18 [self.imageView pjx_setImageWithURL:[NSURL URLWithString:baiduLogoString] placeholderImage:[UIImage imageNamed:@"placeholderImage"]]; 19 } 20 21 @end
效果以下:
在没有网络(左)和有网络(右)状况下的对比图
而后我喜滋滋地去看SDWebImage的sd_setImageWithURL:placeholderImage:实现,结果~~他竟然调用的是另一个巨多参数的函数:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
大概猜下,除了url和placeholder两个参数懂是什么意思,options不明白,progress确定表示的是下载的进度,也就是正在下载时候所要处理的事情(block),那么completed应该表示的是下载完成后所要作的事(block)。
因而我定位到了该函数,发现本身彻底不是一个级别上的,看不懂。不过我还不死心,因而我全局搜索dataWithContentsOfURL,嗯,SDWebImage竟然没有用!好吧,先无论了,不用就不用,那你总得给UIImageView的image赋值吧,并且确定要赋值一次placeholderImage和网络请求获得的image吧。果真找到了,就在上面那个巨多参数的函数中。我只截取了部分代码
1 ...... 2 if (!(options & SDWebImageDelayPlaceholder)) { 3 dispatch_main_async_safe(^{ 4 self.image = placeholder; 5 }); 6 } 7 ..... 8 else if (image) { 9 wself.image = image; 10 [wself setNeedsLayout]; 11 } else { 12 if ((options & SDWebImageDelayPlaceholder)) { 13 wself.image = placeholder; 14 [wself setNeedsLayout]; 15 } 16 } 17 ...
能够看到这里实现了三处image的赋值。而且后面两处赋值后当即使用setNeedsLayout来进行刷新(我注释了刷新代码,好像没有发生什么问题,不过这里仍是注意一下,确定是某个情形下会发生没法自动刷新图片的状况,才要手动刷新)。好的,这里咱们能够停一下,看看这些image赋值都是发生在什么状况下的。
这几处赋值都出现了SDWebImageDelayPlaceholder。看下它的注释,首先,它是一个SDWebImageOptions枚举值,而参数options也是一个枚举类型的变量,注定二者是好基友了。话说回来,SDWebImageDelayPlaceholder表示的是什么呢?看注释:
/** * By default, placeholder images are loaded while the image is loading. This flag will delay the loading * of the placeholder image until after the image has finished loading. */
翻译过来就是,默认状况下,当正在加载网络端的image 时,placeholder已经加载到了UIImageView,这个枚举项就是为了不这种默认状况,他将延迟placeholder的加载直到网络端的image加载完成。可能有些抽象,看代码就好了。
在还没发送请求获取网络端图片以前(即网络端的image还没加载),若是options中有SDWebImageDelayPlaceholder这一选项,就不给image赋值,若是没有这一项,那么就给image赋值placeholder。说白了就是下面这段代码:
1 if (!(options & SDWebImageDelayPlaceholder)) { 2 dispatch_main_async_safe(^{ 3 self.image = placeholder; 4 }); 5 }
其中dispatch_main_async_safe就是SDWebImage定义的一个宏,很好理解:若是当前是主进程,就直接执行block,不然把block放到主进程运行。为何要判断是不是主进程?由于iOS上任何UI的操做都在主线程上执行,因此主进程还有一个名字,叫作“UI进程”。
1 #define dispatch_main_async_safe(block)\ 2 if ([NSThread isMainThread]) {\ 3 block();\ 4 } else {\ 5 dispatch_async(dispatch_get_main_queue(), block);\ 6 }
后面咱们看到有一处代码,表示即便options中有SDWebImageDelayPlaceholder这一选项,也给image赋值placeholder,为啥了?由于此时image已经从网络端加载过了,可是网络端获取image没成功,此时才会用placeholder来替代,赤裸裸的备胎,有代码为证。
else { // image已经尝试获取过了,可是没有从网络端获取到 if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } }
而else上面的else if那段代码,就是表示image从网络获取成功,直接赋值给image。
哈哈,不知道大家会不会有疑惑,你怎么知道此处表示向网络获取image的,也就是注释中说的the image is loading?~~我猜的,不过我猜的没错的话,这段获取的代码既然总体赋值给了id <SDWebImageOperation> operation,那多是为了多任务(多个图片加载),为何呢?我怀疑SDWebImageOperation是一个NSOperation子类(这样才能放到NSOperationQueue中进行多任务嘛)。大家确定说我是SB,这一看就是一个protocol嘛!确实是我猜错了,可是我隐约以为既然叫Operation,不跟NSOperation有点关系也说不清啊,或者它可能模仿了NSOperation的多任务运行方式。以上都是猜想,咱们仍是来看代码(后面会揭秘)。
以上的代码(还有几处没说,可是涉及到什么SDImageCacheType还有其余的,暂时不去想)封装成的operation做为参数放到了sd_setImageLoadOperation中。咱们接着跳到sd_setImageLoadOperation函数中。很简单,只有三行,我直接贴代码了:
1 - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key { 2 [self sd_cancelImageLoadOperationWithKey:key]; 3 NSMutableDictionary *operationDictionary = [self operationDictionary]; 4 [operationDictionary setObject:operation forKey:key]; 5 }
虽然不少变量和函数不认识,可是咱们大概也能猜到这三行作了什么。我先看[self operationDictionary],具体定义不要看,咱们知道它是一个NSMutableDictionary便可,并且既然叫operationDictionary,那么存放的必定是各类operation的序列了(固然也就包括SDWebImageOperation类型的operation),并且这些operation是根据key来索引的。好的,咱们回到函数中。一进函数,先取消索引为key的operation的操做,有些人说,若是我以前正在进行索引为key的操做,那不就取消了嘛?是啊,就是这样,若是该operation存在,就取消掉了,还要删除这个key对应的object(operation)。而后从新设置key对应的operation。咱们能够看看函数sd_cancelImageLoadOperationWithKey。(这一段文字我解释得很差,下面评论区有详细解释)。
1 - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key { 2 // Cancel in progress downloader from queue 3 NSMutableDictionary *operationDictionary = [self operationDictionary]; 4 id operations = [operationDictionary objectForKey:key]; 5 if (operations) { 6 if ([operations isKindOfClass:[NSArray class]]) { 7 for (id <SDWebImageOperation> operation in operations) { 8 if (operation) { 9 [operation cancel]; 10 } 11 } 12 } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ 13 [(id<SDWebImageOperation>) operations cancel]; 14 } 15 [operationDictionary removeObjectForKey:key]; 16 } 17 }
代码也很容易理解,先获取到operation的序列,即[self operationDictionary]。而后根据key来索引到对应的operation,若是operation存在的话。就要取消该operation。这里有一个注意的地方,也是我以前没想到的,就是索引到的operation其实一组operation的集合,那么就须要来个遍历一个个取消掉operation序列中的operation了。最后移除key对应的object。
这里我有个疑惑:为啥operation都是id<SDWebImageOperation>?并且,大家也注意到了SDWebImageOperation只有一个cancel接口。为何要这样设计,还有待进一步研究。
咱们仍是回到sd_setImageWithURL这个函数中,如今咱们有个大概思路了。咱们来看看咱们可以理解的部分:
未完待续,请君移步【原】SDWebImage源码阅读(二)。