arm开发板放张图片动起来_SDWebImage内部实现及其原理

SDWebImage是一个强大的图片加载库,本文详细介绍了其工作原理、核心功能和使用方法,包括内存缓存、磁盘缓存、下载管理、问题解决策略等方面。通过理解其内部实现,有助于提升开发效率并优化图片加载体验。
摘要由CSDN通过智能技术生成

SDWebImage的知名度就不用说了,github上近10k的star,国内外太多的App使用其进行图片加载。简单介绍一下,它是一个图片框架,支持从网络中下载且缓存图片,并设置图片到对应的UIImageView 控件或者 UIButton 控件。使用SDWebImage来管理图片加载,会极大地提高我们的开发效率,从而让我们更加专注于业务逻辑实现。也正是因为这样,让我们很多开发都只会用SDWebImage,而忽略了它的内部实现,今天我就给大家介绍一下SDWebImage的内部实现及原理。

一、SDWebImage 概述

SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能。它具有以下功能:

1、提供了一个UIImageView的 category用来加载网络图片,并对网络图片的缓存进行管理。

2、采用异步方式来下载网络图片

3、采用异步方式,使用 memory+disk 来缓存网络图片,自动管理缓存。

4、支持 GIF 动画

5、支持 WebP 格式

6、同一个 URL 的网络图片不会被重复下载

7、失效的 URL 不会被无限重试

8、耗时操作都在子线程,确保不会阻塞主线程

9、使用 GCD 和 ARC

10、支持 Arm64

二、SDWebImage 使用

1、使用 ImageView+WebCache category 来加载 网络图片

UIImageView调用方法.png

2.、使用Blocks,在 block 中得到图片下载进度和图片加载完成(下载完成或者读取缓存)的回调,如果你在图片加载完成前取消了请求操作,就不会收到成功或失败的回调

Block方式给UIImageView设置图片.png

3、使用SDWebImageManager,SDWebImageManager为UIImageView+WebCache category的实现提供下载图片接口。

SDWebImageManager下载图片.png

4、单独使用 SDWebImageDownloader 来下载图片,但是图片内容不会缓存。

SDWebImageDownloader下载图片.png

5、使用 SDImageCache 异步缓存图片

SDImageCache 同时缓存到内存和磁盘中。

图片存入内存和磁盘.png

SDImageCache 只缓存到内存:

图片只存入内存.png

读取缓存时可以使用 queryDiskCacheForKey:done: 方法,图片缓存的 key 是唯一的,通常就是图片的 absolute URL。

读取缓存.png

6、清除缓存文件

清楚缓存文件.png

7、获取所有缓存图片的总大小

获取缓存图片总大小.png

8、获取缓存图片张数

获取缓存图片张数.png

9、直接从缓存中提取图片

从缓存提取图片.png

10、直接删除缓存中得图片

删除缓存中的图片.png

11、判断本地缓存中是否存在网络中的图片

是否存在网络图片.png

在实际的运用中,我们并不直接使用SDWebImageDownloader类及SDImageCache类来执行图片的下载及缓存。为了方便用户的使用,SDWebImage提供了SDWebImageManager对象来管理图片的下载与缓存。而且我们经常用到的诸如UIImageView+WebCache等控件的分类都是基于SDWebImageManager对象的。该对象将一个下载器SDWebImageDownloader和一个图片缓存SDImageCache绑定在一起,并对外提供两个只读属性来获取它们。

三、实现原理

1、SDWebImage 流程图

SDWebImage流程图.png

2、核心逻辑

1、UIImageView+WebCache 中最核心的方法 sd_setImageWithURL: placeholderImage: options: progress: completed:。给UIImageView设置图片,就是调用此方法,方法内部的具体做以下几件事:

取消当前正在进行的加载任务 operation

设置 placeholder

如果 URL 不为 nil,就通过 SDWebImageManager 单例开启图片加载任务 operation,SDWebImageManager 的图片加载方法中会返回一个 SDWebImageCombinedOperation 对象,这个对象包含一个 cacheOperation 和一个 cancelBlock。

给UIImageView设置图片.png

a.开始加载之前,先取消对应的UIImageView先前的图片下载操作。框架将对应的下载操作放在UIView的一个自定义字典属性(operationDictionary)中,取消下载操作第一步也是从这个UIView的自定义字典属性(operationDictionary)中取出所有的下载操作,然后依次调用取消方法,最后将取消的操作从(operationDictionary)字典属性中移除。

最终取消图片下载方法.png

b.移除之前没用的图片下载操作之后就创建一个新的图片下载操作,然后设置到UIView的一个自定义字典属性(operationDictionary)中。创建一个新的图片下载操作,框架保存了一个失效的URL列表,如果URL失效了就会被加入这个列表,保证不会重复多次请求失效的URL。根据给定的URL生成一个唯一的Key,之后利用这个key到缓存中查找对应的图片缓存。

图片下载操作.png

c.读取图片缓存,先拿图片缓存的 key去内存中读取缓存,如果有,就返回给 SDWebImageManager ;如果内存没有缓存,就开启异步线程去磁盘开始读取,如果有就从磁盘同步到内存中去,再返回给SDWebImageManager 。如果内存和磁盘中都没有,SDWebImageManager 就会调用 downloadImageWithURL: options: progress: completed: 方法去下载。

读取图片缓存.png

d.在图片下载方法中,调用了一个方法用于添加创建和下载过程中的各类Block回调。

下载状态回调Block.png

e.如果该URL是第一次加载的话,那么就会执行createCallback这个回调Block,然后在createCallback里面开始构建网络请求,创建一个 NSMutableURLRequest 对象和一个 SDWebImageDownloaderOperation 对象,并将该 SDWebImageDownloaderOperation 对象添加到 SDWebImageDownloader 的downloadQueue 来启动异步下载任务,在下载过程中执行各类进度Block回调。

第一次加载时构建请求.png

f.当图片下载完成之后会回到done的Block回调中做图片转换处理和缓存操作。

下载完成的回调.png

g.回到UIImageView控件的设置图片方法Block回调中,给对应的UIImageView设置图片,操作流程到此完成。

设置图片.png

3、实现细节

SDWebImage 最核心的功能也就是以下 4 件事:

下载(SDWebImageDownloader)

缓存(SDImageCache)

将缓存和下载的功能组合起来(SDWebImageManager)

封装成 UIImageView 等类的分类方法(UIImageView+WebCache 等)

3.1 SDWebImageDownloader

3.1.1 SDWebImageDownloader 继承于 NSObject,主要承担了异步下载图片和优化图片加载的任务。

SDWebImageDownloader 通过+initialize 和 -init 这两个方法实现注册通知和做一些初始化设置以及默认设置。SDWebImageDownloader包括设置最大并发数(6)、下载超时时长(15s)等。

SDWebImageDownloader这个类里面除了这两个方法,最核心的方法就是downloadImageWithURL:options: progress: completed:方法,这个方法中首先调用 -addProgressCallback: andCompletedBlock: forURL: createCallback: 方法来保存每个 url 对应的回调 block,-addProgressCallback 方法会先进行错误检查,判断 URL 是否为空,如果为 空, 则直接回调 completedBlock,返回失败的结果,然后 return,否则将 URL 对应的 progressBlock 和 completedBlock 保存到 URLCallbacks 属性中去。

有个细节要注意,因为可能同时下载多张图片,所以就可能出现多个线程同时访问 URLCallbacks 属性的情况。为了保证线程安全,所以这里使用了 dispatch_barrier_sync 来分步执行添加到 barrierQueue 中的任务,这样就能保证同一时间只有一个线程能对 URLCallbacks 进行操作。

(dispatch_barrier_sync 将某个任务插入某个队列中执行:想了解的可以看看这篇文章 https://blog.csdn.net/u013046795/article/details/47057585)

如果这个 URL 是第一次被下载,就要回调 createCallback,createCallback 主要做的就是创建并开启下载任务,下面是 createCallback 的具体实现逻辑:

1.创建一个 NSMutableURLRequest 对象和一个 SDWebImageDownloaderOperation 对象,并将该 SDWebImageDownloaderOperation 对象添加到 SDWebImageDownloader 的downloadQueue 来启动异步下载任务。

2.SDWebImageDownloaderOperation 中有3个block回调,分别是progressBlock(接收到的数据大小和预计数据大小),completedBlock(图片 UIImage,图片数据 NSData,错误 NSError,是否结束 isFinished),cancelBlock(移除 url 对应的所有回调 block);这三个block都用了weak-strong dance,使用 strongSelf 强引用 weakSelf,目的是为了保住 self 不被释放,然后检查 self 是否已经被释放(这里为什么先“保活”后“判空”呢?因为如果先判空的话,有可能判空后 self 就被释放了)

(对weak-strong dance不理解可以看看 https://www.jianshu.com/p/4e6153ea2734)

设置下载完成后是否需要解压缩

如果设置了 username 和 password,就给 operation 的下载请求设置一个 NSURLCredential 身份认证

设置 operation 的队列优先级

将 operation 加入到队列 downloadQueue 中,队列(NSOperationQueue)会自动管理 operation 的执行

如果 operation 执行顺序是先进后出,就设置 operation 依赖关系(先加入的依赖于后加入的),并记录最后一个 operation(lastAddedOperation)

3.1.2 SDWebImageDownloaderOperation

当创建的 SDWebImageDownloaderOperation 对象被加入到 downloader 的 downloadQueue 中时,该对象的 -start 方法就会被自动调用。

-start 方法中首先创建了用来下载图片数据的 NSURLSession,然后开启 connection,同时发出开始图片下载的 SDWebImageDownloadStartNotification 通知,为了防止非主线程的请求被 kill 掉,这里开启 runloop 保活,直到请求返回。

一张图片的数据下载是由一个 NSConnection 对象来完成的,这个对象的整个生命周期(从创建到下载结束)又是由 SDWebImageDownloaderOperation 来控制的,将 operation 加入到 operation queue 中就可以实现多张图片同时下载了。

简单概括成一句话就是,NSURLSession 负责网络请求,NSOperation 负责多线程。

(为什么要用runloop :https://blog.ibireme.com/2015/05/18/runloop/)

3.2 SDImageCache

3.2.1 SDImageCache 管理着一个内存缓存和磁盘缓存(可选),同时在写入磁盘缓存时采取异步执行,所以不会阻塞主线程,影响用户体验。为什么需要缓存?

以空间换时间,提升用户体验:加载同一张图片,读取缓存是肯定比远程下载的速度要快得多的

减少不必要的网络请求,提升性能,节省流量:一般来讲,同一张图片的 URL 是不会经常变化的,所以没有必要重复下载。另外,现在的手机存储空间都比较大,相对于流量来,缓存占的那点空间算不了什么。

3.2.2 SDImageCache 的内存缓存是通过一个继承 NSCache 的 AutoPurgeCache 类来实现的,NSCache 是一个类似于 NSMutableDictionary 存储 key-value 的容器,主要有以下几个特点:

自动删除机制:当系统内存紧张时,NSCache会自动删除一些缓存对象

线程安全:从不同线程中对同一个 NSCache 对象进行增删改查时,不需要加锁

不同于 NSMutableDictionary,NSCache存储对象时不会对 key 进行 copy 操作

3.2.3 SDImageCache 的磁盘缓存是通过异步操作 NSFileManager 存储缓存文件到沙盒来实现的。

3.2.4 磁盘清理

(1).清扫磁盘缓存(clean)和清空磁盘缓存(clear)是两个不同的概念,清空是删除整个缓存目录,清扫只是删除部分缓存文件。

(2).清扫磁盘缓存有两个指标:一是缓存有效期,二是缓存体积最大限制。SDImageCache中的缓存有效期是通过 maxCacheAge 属性来设置的,默认值是 1 周,缓存体积最大限制是通过 maxCacheSize 来设置的,默认值为 0。

一般在应用即将终止时和退到后台时,都会调用 -cleanDiskWithCompletionBlock: 方法来异步清扫缓存,清扫磁盘缓存的逻辑是,先遍历所有缓存文件,并根据文件的修改时间来删除过期的文件,同时记录剩下的文件的属性和总体积大小,如果设置了 maxCacheAge 属性的话,接下来就把剩下的文件按修改时间从小到大排序(最早的排最前面),最后再遍历这个文件数组,一个一个删,直到总体积小于 desiredCacheSize 为止,也就是 maxCacheSize 的一半。

(3).清理内存缓存,一般在系统抛出内存警告的时候,SDWebImage会自动调用清除内存缓存方法,等系统抛出内存警告可能有点困难,所以可以根据需求,自己调用[[SDImageCache sharedImageCache] clearMemory]; ,不过这个方法只是清除了SDWebImage的缓存,并没有清除系统缓存。如果需要清楚系统缓存可以调用

[[NSURLCache sharedURLCache] removeAllCachedResponses];来实现。

3.3 SDWebImageManager

SDWebImageManager是图片加载管理器,主要是将下载和缓存两个功能结合起来,才算是一个完整的图片加载器。

SDWebImageManager 的核心任务是由 -downloadImageWithURL:options:progress:completed: 方法来实现的,这个方法中先会从 SDImageCache 中读取缓存,如果有缓存,就直接返回缓存,如果没有就通过 SDWebImageDownloader 去下载,下载成功后再保存到缓存中去,然后再回调 completedBlock。其中 progressBlock 的回调是直接交给了 SDWebImageDownloader 的 progressBlock 来处理的。

SDWebImageManager 在读取缓存和下载之前会创建一个 SDWebImageCombinedOperation 对象,这个对象是用来管理缓存读取操作和下载操作的,SDWebImageCombinedOperation 对象有 3 个属性:

cancelled:用来取消当前加载任务的

cancelBlock:用来移除当前加载任务和取消下载任务的

cacheOperation:用来取消读取缓存操作

3.4 UIImageView+WebCache

UIImageView+WebCache就是我们平时最常用的图片加载,是通过调用 UIImageView+WebCache 的 -sd_setImageWithURL:... 系列方法来加载的,UIImageView+WebCache 实际上是将 SDWebImageManager 封装了一层,内部针对 UIImageView 做了一些处理,使用起来更方便、更直接、更简单。

UIImageView+WebCache 的核心逻辑都在 - sd_setImageWithURL:placeholderImage:options:progress:completed: 方法中,为了防止多个异步加载任务同时存在时,可能出现互相冲突和干扰,该方法中首先通过调用 -sd_cancelCurrentImageLoad 方法取消这个 UIImageView 当前的下载任务,然后设置了占位图,如果 url 不为 nil,接着就调用 SDWebImageManager 的 -downloadImage... 方法开始加载图片,并将这个加载任务 operation 保存起来,用于后面的 cancel 操作。图片获取成功后,再重新设置 imageView 的 image,并回调 completedBlock。

需要注意的是为了防止多个异步加载任务同时存在时,可能出现互相冲突和干扰,每个 UIImageView 的图片加载任务都会保存成一个 Associated Object,方便需要时取消任务。这个 Associated Object 的操作是在 UIView+WebCacheOperation 中实现的,因为除了 UIImageView 用到图片加载功能之外,还有 UIButton 等其他类也用到了加载远程图片的功能,所以需要进行同样的处理,这样设计实现了代码的复用。

四、遇到的问题

问题 1:使用SDWebImage加载较多图片时,会出现内存警告;

解决方案:定期调用 [[SDImageCache sharedImageCache] setValue:nil forKey:@"memCache"];

问题 2:图片刷新问题:SDWebImage 在进行缓存时忽略了所有服务器返回的 caching control 设置,并且在缓存时没有做时间限制,这也就意味着图片 URL 必须是静态的了,要求服务器上一个 URL 对应的图片内容不允许更新。但是如果存储图片的服务器不由自己控制,也就是说 图片内容更新了,URL 却没有更新,这种情况怎么办?

解决方案:在调用 sd_setImageWithURL: placeholderImage: options:方法时设置 options 参数为SDWebImageRefreshCached,这样虽然会降低性能,但是下载图片时会照顾到服务器返回的 caching control。

问题 3:在加载图片时,如何添加默认的 progress indicator ?

解决方案:在调用 -sd_setImageWithURL:方法之前,先调用下面的方法:

[imageView sd_setShowActivityIndicatorView:YES];

[imageView sd_setIndicatorStyle:UIActivityIndicatorViewStyleGray];

五、总结

SDWebImage作为一个优秀的图片加载框架,提供的使用方法和接口对开发者来说非常友好。

其内部实现多是采用Block的方式来实现回调,代码阅读起来可能没有那么直观。

内部有用到了很多好的技术,比如,GCD,线程加锁等,可供我们学习。

SDWebImage中有两个比较好用的宏定义定义之dispatch_main_sync_safe和dispatch_main_async_safe。

define dispatch_main_sync_safe(block)\

if ([NSThread isMainThread]) {\

block();\

} else {\

dispatch_sync(dispatch_get_main_queue(), block);\

}

define dispatch_main_async_safe(block)\

if ([NSThread isMainThread]) {\

block();\

} else {\

dispatch_async(dispatch_get_main_queue(), block);\

}

SDWebImage频繁运用了runtime中的关联函数,用关联函数关联一个对象,称关联对象,关联对象相当于实例变量,在类别里面,不能创建实例变量,关联对象就可以解决这种问题。

主要函数有:

objc_setAssociatedObject 相当于 setValue:forKey 进行关联value对象

objc_getAssociatedObject 用来读取对象

objc_AssociationPolicy 属性 是设定该value在object内的属性,即 assgin, (retain,nonatomic)...等

objc_removeAssociatedObjects 函数来移除一个关联对象,或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。

相关参数:

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

id object :表示关联者,是一个对象,变量名理所当然也是object

const void *key :获取被关联者的索引key

id value :被关联者,这里是一个block

objc_AssociationPolicy policy : 关联时采用的协议,有assign,retain,copy等协议,一般使 用OBJC_ASSOCIATION_RETAIN_NONATOMIC。

相关协议:

OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。

OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。

OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。

OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。

OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。

六、SDWebImage3.8.2与4.0的区别

SDWebImage4.0是一个大版本,在结构上和API方面都有所改动。

区别:

1、新增targetURL,存放原始图像url;

2、新增一个FLAnimatedImageView+WebCache类,主要在动图支持上做了改进,尤其是 GIF;

3、SDWebImage4.0新增一个UIView+WebCache类中,将UIImageView+WebCache类中的方法移动到UIView+WebCache类中,UIImageView对象依然响应这些方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值