
这个类库提供一个UIImageView类别以支持
加载来自网络的远程
图片。具有
缓存管理、异步下载、同一个URL下载次数控制和优化等特征。使用示范的代码:UITableView使用UIImageView+WebCache类(基本应用,UIImageView的一个category)前提#import导入UIImageView+WebCache.h文件,然后在tableview的cellForRowAtIndexPath:方法下:-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{staticNSString*MyIdentifier=@"MyIdentifier"UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:MyIdentifier]if(cell==nil){cell=[[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:MyIdentifier]autorelease]}//HereweusethenewprovidedsetImageWithURL:methodtoloadthewebimage[cell.imageViewsetImageWithURL:[NSURLURLWithString:@"/path/to/image.jpg"]placeholderImage:[UIImageimageNamed:@"placeholder.png"]cell.textLabel.text=@"MyText"returncell}基本代码:[imageViewsetImageWithURL:[NSURLURLWithString:@"/path/image.jpg"]使用SDWebImageManager类:可以进行一些异步加载的工作。SDWebImageManager*manager=[SDWebImageManagersharedManager]UIImage*cachedImage=[managerimageWithURL:url]//将需要缓存的图片加载进来if(cachedImage){//如果Cache命中,则直接利用缓存的图片进行有关 *** 作//Usethecachedimageimmediatly}else{//如果Cache没有命中,则去下载指定网络位置的图片,并且给出一个委托方法//Startanasyncdownload[managerdownloadWithURL:urldelegate:self]}当然你的类要实现SDWebImageManagerDelegate协议,并且要实现协议的webImageManager:didFinishWithImage:方法。//当下载完成后,调用回调方法,使下载的图片显示-(void)webImageManager:(SDWebImageManager*)imageManagerdidFinishWithImage:(UIImage*)image{//Dosomethingwiththedownloadedimage}独立的异步图像下载可能会单独用到异步图片下载,则一定要用downloaderWithURL:delegate:来建立一个SDWebImageDownloader实例。downloader=[SDWebImageDownloaderdownloaderWithURL:urldelegate:self]这样SDWebImageDownloaderDelegate协议的方法imageDownloader:didFinishWithImage:被调用时下载会立即开始并完成。独立的异步图像缓存SDImageCache类提供一个创建空缓存的实例,并用方法imageForKey:来寻找当前缓存。UIImage*myCachedImage=[SDImageCachesharedImageCache]imageFromKey:myCacheKey]存储一个图像到缓存是使用方法storeImage:forKey:[SDImageCachesharedImageCache]storeImage:myImageforKey:myCacheKey]默认情况下,图像将被存储在内存缓存和磁盘缓存中。如果仅仅是想内存缓存中,要使用storeImage:forKey:toDisk:方法的第三个参数带一负值来替代。SDWebImage支持异步的图片下载+缓存,提供了UIImageView+WebCacha的category,方便使用。纪录一下SDWebImage加载图片的流程。入口setImageWithURL:placeholderImage:options:会先把placeholderImage显示,然后SDWebImageManager根据URL开始处理图片。进入SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给SDImageCache从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate回调imageCache:didFindImage:forKey:userInfo:到SDWebImageManager。SDWebImageManagerDelegate回调webImageManager:didFinishWithImage:到UIImageView+WebCache等前端展示图片。如果内存缓存中没有,生成NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存。根据URLKey在硬盘缓存目录下尝试读取图片文件。这一步是在NSOperation进行的 *** 作,所以回主线程进行结果回调notifyDelegate:。如果上一 *** 作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调imageCache:didNotFindImageForKey:userInfo:。共享或重新生成一个下载器SDWebImageDownloader开始下载图片。图片下载由NSURLConnection来做,实现相关delegate来判断图片下载中、下载完成和下载失败。connection:didReceiveData:中利用ImageIO做了按图片下载进度加载效果。connectionDidFinishLoading:数据下载完成后交给SDWebImageDecoder做图片解码处理。图片解码处理在一个NSOperationQueue完成,不会拖慢主线程UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。在主线程notifyDelegateOnMainThreadWithInfo:宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:回调给SDWebImageDownloader。imageDownloader:didFinishWithImage:回调给SDWebImageManager告知图片下载完成。通知所有的downloadDelegates下载完成,回调给需要的地方展示图片。将图片保存到SDImageCache中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独NSInvocationOperation完成,避免拖慢主线程。SDImageCache在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。SDWI也提供了UIButton+WebCache和MKAnnotationView+WebCache,方便使用。SDWebImagePrefetcher可以预先下载图片,方便后续使用。SDWebImage库的作用:通过对UIImageView的类别扩展来实现异步加载替换图片的工作。在iOS开发中, 图片(UIImage) 是我们在开发中,占用手机 内存 比较大的对象,如果在运行过程中,内存占用过大,对 电池寿命 会造成影响,如果超过了 内存占用的最大值 ,会造成App的 crash 。这篇文章从 图片的加载 原理和 SDWebImage 的源码实现的角度来介绍图片加载。
在iOS中使用 UIImage 和 UIImageView 来记载图片,他俩遵守经典的 MVC 架构, UIImage 相当于 Model , UIImageView 相当于 View :
UIImage 负责 加载图片 , UIImageView 负责 渲染图片 。
图片的渲染流程分为 3个阶段 : 加载(Load),解码(Decoder)和渲染(Render)
在每个阶段都会有相对应的 缓冲区 : 数据缓冲区(DataBuffer),图像缓冲区(imageBuffer)和帧缓冲区(framebuffer) 。
我们以加载一个图片的尺寸为: 2048 px * 1536 px ,在磁盘上的大小为: 590kb 的图片为例,来分析前两个阶段的缓冲区。
DataBuffer 只是一种包含 一系列字节 的缓冲区。通常以某些 元数据 开头, 元数据 描述了存储在数据缓冲区中的图像大小,包含图形数据本身, 图像数据以某种形式编码 如 JPEG压缩或PNG,这意味着, 该字节并不直接描述图像中像素的任何内容 。此时的 DataBuffer 大小为 590kb 。
在 SDWebImage 中,图片加载完成后,在 sd_imageFormatForImageData 的方法中,是通过 DataBuffer 的 第一个字节 来判断图片的格式的。
在 图片加载 完后,需要将 Data Buffer 的 JPEG,PNG或其他编码的数据 ,转换为 每个像素 的 图像信息 ,这个过程,称为 Decoder(解码) ,将 像素信息 存放在 ImageBuffer 。
图片占用的内存大小与 图像的尺寸有关 ,与它的 文件大小无关 ,在iOS SRGB 显示格式中 (4byte空间显示一个像素) ,如果解析所有的像素,需要 2048 px * 1536 px * 4 byte/px = 10MB 的空间,此时的 ImageBuffer 的大小为 10MB 。
在 ImageBuffer 解析完后,提交给 frameBuffer 进行渲染显示。
总的来说,图片加载过程和消耗的内存如下图所示:
在 Xcode 工程中,当push新页面的时候,只加载一个图片。
加载前内存值:
加载后内存值:
大多数情况下,我们并不需要如此高精度的显示图片,占用了这么多的内存,能否减少加载图片时占用的内存值呢?
在苹果官方文档中,建议我们使用 向下采样(Downsampleing) 的技术,来加载图片,减少 ImageBuffer 的大小。
方法如下:
我们来测试一下:
加载之前时是 13M ,加载之后是 17M ,效果是很明显的,节省了大约 5M 的内存空间。
在对图片进行压缩时,我们应首选向下采样技术 。
在 SDWebIamge 中,一共有3种类型的解码器: SDImageIOCoder, SDImageGIFCoder, SDImageAPNGCoder ,根据 DataBuffer 的编码类型,使用相对应的编码器。
在 -(UIImage *)decodedImageWithData:(NSData *)data 方法中,配置解码参数,开始进行解码 *** 作。
在 + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary )options 中,完成图像解码
在 iOS中,渲染图片格式有4种
正确的思路是: 不选择渲染格式,让渲染格式选择你 。
使用 UIGraphicsImageRender 来替换 UIGraphicsBeginImageContextWithOptions ,前者在 iOS12 以后,会自动选择渲染格式,后者默认都会选择 SRGB Format 。
此时,系统为自动选择 Alpha 8 Format 格式,内容空间占用,将会减少 75% 。
在需要绘制带有子视图的View时,不使用 draw(rect:) 方法,使用 系统的View属性 或者 添加子视图 的方式,将绘制工作交给系统来处理。
背景色直接通过 UIView.backgroundColor 设置,而非使用 draw(rect:)
我们在开发中,一般会对图片进行 子线程异步加载 ,在后台进行 解码和下采样 。在列表中,有时会加载很多图片,此时应该注意 线程爆炸 问题。
当我们要求 系统去做比CPU能够做的工作更多的工作时 就会发生这种情况,比如我们要显示 8张图片 ,但我们只有 两个CPU ,就不能一次完成所有这些工作,无法在不存在的CPU上进行并行处理, 为了避免向一个全局队列中异步的分配任务时发生死锁 , GCD 将创建新线程来捕捉我们要求它所做的工作,然后CPU将花费大量时间,在这些 线程 之间进行 切换 ,尝试在所有工作上取得我们要求 *** 作系统为我们做的 渐进式进展 ,在这些线程之间 不停切换 ,实际上是相当大的开销,现在 不是简单地将工作分派到全局异步队列之一 ,而是 创建一个串行队列 ,在预取的方法中,异步的将工作分派到该队列,它的确意味着单个图像的加载,可能要比以前晚才能开始取得进展,但CPU将花费更少的时间,在它可以做的小任务之间来回切换。
在 SDWebImage 中,解码的队列 _coderQueue.maxConcurrentOperationCount = 1 就是一个串行队列。这样就很好的解决了 多图片异步解码 时, 线程爆炸 问题。
评论列表(0条)