今天和大家一起来讨论一下iOS图片加载策略,如果问题之处,还望大家不吝赐教!
在深入了解sdwebimage源码后,我自己想尝试摸索着编写一个图片缓存框架!
这个是github上的demo地址:demo地址
一 背景介绍
对于图片加载策略,目前估计用的最多的便是SDWebImage。我之所以要写这篇博客主要便是为了加深对SDWebImage的理解,并且自己搭建一个基于该种设计思想的图片加载框架。这有助于大家了解SDWebImage的设计流程,还可以基于此认知自己搭建一个简单的图片加载框架。
二 SDWebImage的主要逻辑
SDWebImage 主要有以下特点:
1 提供一个UIimageView的分类来加载网络图片并且也对网络缓存进行相应的管理。
2 采用异步方式下载网络图片
3 采用异步方式,使用内存+磁盘存储的方式对下载好的网络图片进行缓存管理
4 同一个URL的网络图片不会被重复下载(细节把控)
5 对于UITableview的cell重用时,不会因为图片是异步下载而出现错乱的情况(细节把控)
6 对于失效的URL不会被重复下载
7 所有的耗时操作都放置到子线程完成,确保主线程的运行流畅
SDWebImage的处理流程图如下:
2.1 SDWebImage流程图SDWebImage的主要步骤如图2.1所示,
1 imageView 调用 sd_setImageWithURL: placeholderImage: ,这时框架会删除该控件原先的下载任务, 设置placeholder。
2 检测Url合法性,从SDImageCache中寻找缓存(内存缓存用NSCache(key 为url),Disk缓存用NSFileManager写入文件)
3 没有缓存则从网上下载:SDWebImageDownLoader下载器是单例类,主要负责下载操作管理。图片的下载操作放在一个NSOperationqueue队列中,最大并发数为6,向队列中添加NSOperation。下载操作依赖于NSURLSession对象,下载完成后则对图像数据进行解压缩操作。再写入缓存,回调显示图片。
4 当有内存警告则清理NSCache,进入后台或程序进程结束则清理过期文件。
SDWebImage常见面试问题:
1 SDWebImage 任务的执行? 两种方式:先进先出(队列) 先进后出(栈) 通过[operation2 addDependency:operation1] 给operation设置依赖
2 下载操作默认超时时间 15s
3 如何保证错误的url不会被尝试重新下载?内部设置一个黑名单 NSMutableSet
4 其下载操作队列的最大并发量为6
5 SDWebImage如何区别不同格式图像的?根据图像二进制数据的第一个字节来判断的。
三 CLWebImage的构建
我花了三天左右时间将SDWebImage摸了个遍(其实之前也看过好几遍源码),该框架的设计确实很优秀,不是我等菜鸟能够轻易写出的。CLWebImage主要逻辑也是模仿SDWebImage的处理流程。
首先,调用UIImageView的分类方法,设置图片Url 和 placeHolder占位图;
-(void)cl_setImageWithURL:(NSString *)urlStr placeHolder:(UIImage *)imageHolder completion:(CLLoadImageCallBack)completion;
A操作流程解释:首先cl_cancelCurrentImageLoad方法会取消该控件之前的图片设置操作,因为每个控件的下载操作是异步的,控件设置图片的快慢还要取决该控件所下载图片的大小,当UITableView的cell在下载一张Image时被划到重用池中,之后该cell又被取出重用设置了一张来自缓存的图片,但是此时之前的下载操作完成,于是便把该下载好的图片设置到该cell上,故此造成了cell的图片显示错乱。解决此问题的方法是:我建立一个UIView(CLCacheOperation)的分类,将UIView与CLDownloadOperation对象进行关联,该CLDownloadOperation对象含有一个判断标识isCancelled,每一个图片下载操作完成之后便会根据isCancelled的状态来决定是否将该图片设置到控件上。
图3.2 CLDownloadOperation对象具体的取消操作内容如下:
图3.3 取消操作该A操作与D操作对应,D操作即设置每个UIView所对应的操作,具体如上图3.3所示。
接下来是B操作:设置默认的占位图到控件上。
最后是最重要的C操作:CLImageManager是一个图片单例管理器,主要用来决定是从内存缓存中、磁盘缓存中获取图片,还是下载图片。进入[CLImageManager.shareInstance loadImageWithUrl:urlStrCompleted:^(UIImage*image,NSData*data,NSError*error,BOOLfinished,NSURL*imageUrl) 的方法,具体实现如下图所示:
图3.4 CLImageManager 加载图片方法该处理流程E部分属于一些容错判断,F部分即判断当前图片的Url是否属于之前遇到的错误的Url,如果是则直接结束处理,并且返回1001的错误代码。
接下来处理如下图所示:
图3.5 CLImageManager 加载图片方法该方法中G操作,即根据图片的url获取一个唯一的key,通过这个key可以从H操作中获取到内存缓存,我的内存缓存采用NSCache编写的,如果H操作可以获取到缓存图片,则直接设置到控件上(利用回调方法)。如果内存缓存中没有,则利用J操作从磁盘缓存中查找有没有图片(磁盘缓存利用NSFileManager编写),如果磁盘中存在该图片则设置到控件上,并且将其缓存到内存中去,处理结束;如果不存在,则继续下面操作:
图3.6 CLImageManager 加载图片方法该部分是在缓存中判断出不存在该url所对应的图片之后执行的。首先runOperationsDic中存有对应的操作对象CLDownloadOperation(采用key-value的存储方式),如果发现已存在相同url的下载操作正在进行,则不再开启新的下载任务。这样可以有效地防止不同控件下载同一源的图片,最大量的节约用户流量。该部分K操作即 刷新一次本地缓存看看key对应的图片是否下载完成,如果已完成则回调显示图片,否则执行L操作。L操作是给当前控件添加一个监听操作,因为在图片操作完成操作之后会发出通知,以此来获取相同url的图片。
当然,如果该runOperationsDic中不存在对应的操作对象CLDownloadOperation,则开启新的下载任务,具体如下图所示:
图3.7 下载操作该流程中M操作即利用CLDownloader单例下载器下载图片,下载成功之后会通过回调方法设置图片到控件上。如果有错则返回error并且将该url加入黑名单failedUrlSet,以防止下次下载时经历过多处理逻辑,这里便是N操作的作用。 有一点要注意,如果有图片数据下载成功,则一定要缓存到内存和磁盘中,以便后面调用。下载完成后还需要将该操作从runOperationsDic移除,因为runOperationsDic保存的是当前正在进行的处理操作,处理完毕就要从里面清除,即图3.7的P操作。 最后是Q操作,该操作是在[CLDownloader.shareInstance downloadImageWithUrl:url CompletedBlock]之后,需要将该operation以key-value的形式保存到runOperationsDic中去,理由前面也说过了。
以上便是CLWebImage的整个图片处理流程。
当然,当应用出现内存警告时,可以通过如下图的CLImageManager实例方法分别清除内存缓存和磁盘缓存。
图3.8 清除缓存写在最后的话:这里是demo,如果能star一下,不胜感激。关于iOS图片加载策略的思路今天就分享到这里,如果大家发现有什么问题或者更好的方法可以和我交流,共同进步,谢谢大家!
网友评论