2018.9.24html
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; 复制代码
在 block 中获得图片下载进度和图片加载完成(下载完成或者读取缓存)的回调,若是你在图片加载完成前取消了请求操做,就不会收到成功或失败的回调ios
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { ... completion code here ... }]; 复制代码
单独使用 SDWebImageManagergit
SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager loadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; 复制代码
单独使用 SDWebImageDownloader 异步下载图片github
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; [downloader downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { if (image && finished) { // do something with image } }]; 复制代码
单独使用 SDImageCache 异步缓存图片 SDImageCache
支持内存缓存和异步的磁盘缓存(可选),可使用单例,也能够建立一个有独立命名空间的 SDImageCache
实例。 添加缓存的方法:segmentfault
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
复制代码
默认状况下,图片数据会同时缓存到内存和磁盘中,若是你想只要内存缓存的话,可使用下面的方法:数组
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey toDisk:NO]; 复制代码
读取缓存时可使用 queryDiskCacheForKey:done:
方法,图片缓存的 key 是惟一的,一般就是图片的 absolute URL。缓存
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"]; [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) { // image is not nil if image was found }]; 复制代码
MKAnnotationView :地图大头针 属于 MapKit 框架的一个类,继承自 UIView,是用来展现地图上的 annotation 信息的,它有一个用来设置图片的属性 image。 官方文档 MKMapView 的使用安全
流程图图源:J_Knight_:SDWebImage源码解析,讲解清晰,十分感谢。bash
在一个UIImageView调用: sd_setImageWithURL: placeholderImage: options: progress: completed:
markdown
nil
,就经过 Manager
单例开启图片加载的operation在downloadImageWithURL:options:progress:completed:
中会先拿图片缓存的 key (默认是图片 URL)去 Cache
单例中读取内存缓存,若是有,就返回给 Manager
; 若是没有,就开启异步线程,拿通过 MD5 处理的 key 去读取磁盘缓存,若是找到磁盘缓存了,就同步到内存缓存,而后再返回给 Manager
。 downloadImageWithURL
会返回一个 SDWebImageCombinedOperation
对象,这个对象包含一个 cacheOperation 和一个 cancelBlock。
若是内存和磁盘缓存中都没有图片,Manager
就会调用 Downloader
单例的 -downloadImageWithURL: options: progress: completed:
方法去下载,先将传入的 progressBlock
和 completedBlock
保存起来,并在第一次下载该 URL 的图片时,建立一个 NSMutableURLRequest
对象和一个 SDWebImageDownloaderOperation
对象,并将该 SDWebImageDownloaderOperation
对象添加到downloadQueue
来启动异步下载任务。
DownloaderOperation
中包装了一个 NSURLConnection
的网络请求,并经过 runloop 来保持 NSURLConnection
在 start 后、收到响应前不被干掉,下载图片时,监听 NSURLConnection
回调的 -connection:didReceiveData:
方法中会负责 progress 相关的处理和回调,- connectionDidFinishLoading:
方法中会负责将 data 转为 image,以及图片解码操做,并最终回调 completedBlock。
DownloaderOperation
中的图片下载请求完成后,会回调给 Downloader
,而后 Downloader
再回调给 Manager
,Manager
将图片缓存到内存和磁盘上(可选),并回调给 UIImageView
,UIImageView
中再回到主线程设置 image
属性。
SDImageCache
管理着一个内存缓存和磁盘缓存(可选),同时在写入磁盘缓存时采起异步执行,不会阻塞主线程。
为何须要缓存?
内存缓存经过一个继承 NSCache
的 AutoPurgeCache
类实现。
NSCache (NSCache官方文档) NSCache
是一个相似于 NSMutableDictionary
存储 key-value 的容器,特色以下:
NSCache
会自动删除一些缓存对象NSCache
对象进行增删改查时,不需加锁NSMutableDictionary
,NSCache
存储对象时不会对 key 进行 copy 操做磁盘缓存经过异步操做 NSFileManager
存储缓存文件到沙盒实现。
-init
方法中默认调用了 -initWithNamespace:
方法,-initWithNamespace:
方法又调用了 -makeDiskCachePath:
方法来初始化缓存目录路径, 同时还调用了 -initWithNamespace:diskCacheDirectory:
方法来实现初始化。 初始化方法调用栈:-init
-initWithNamespace:
-makeDiskCachePath:
-initWithNamespace:diskCacheDirectory:
复制代码
-initWithNamespace:diskCacheDirectory:
初始化实例变量、属性,设置属性默认值,并根据 namespace 设置完整的缓存目录路径,除此以外还添加了通知观察者,用于内存紧张时清空内存缓存,以及程序终止运行时和程序退到后台时清扫磁盘缓存。
storeImage:
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk{ } 复制代码
写入的参数有三个。 添加内存缓存时,先计算像素,再加进去 添加磁盘缓存时,若是须要在存储以前将传进来的 image
转成 NSData
,而不是直接使用传入的 imageData
,那么就要按不一样的图片格式来转成对应的 NSData
对象。
NSData 用来包装数据,存储的是二进制数据,屏蔽了数据之间的差别,文本、音频、图像等数据均可用NSData来存储。
判断图片格式:根据是否有 alpha 通道以及 imageData
的前8位字节 判断图片格式详解 若是 imageData 为 nil,就根据 image 是否有 alpha 通道来判断图片是不是 PNG 格式的 若是 imageData 不为 nil,就根据 imageData 的前 8 位字节来判断是否是 PNG 格式,由于 PNG 图片有一个惟一签名,前 8 位字节是(十进制): 137 80 78 71 13 10 26 10
拿到 imageData 后,借助 NSFileManager 将图片二进制存储到沙盒,存储的文件名是对 key 进行 MD5 处理后生成的字符串。 默认沙盒路径: Library - Caches
iOS的沙盒机制 SandBox 一种安全体制,规定应用程序只能在为该应用建立的文件夹内读取文件,不能访问其余地方的内容。保存全部的非代码文件,如图片,声音,属性列表和文本文件等。 应用程序向外请求或接收数据都须要通过权限认证。 默认状况下,每一个沙盒含有3个文件夹:Documents, Library 和 tmp
queryDiskCacheForKey
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock { } 复制代码
返回的是一个 NSOperation 对象 这个方法会先读取内存缓存,若是没有再读取磁盘缓存。 读取磁盘缓存时,会先从沙盒中去找,若是沙盒中没有,再从 customPaths
(也就是 bundle)中去找。 找到以后,对数据进行转换,后面的图片处理步骤跟图片下载成功后的图片处理步骤同样——先将 data 转成 image,再进行根据文件名中的 @2x、@3x 进行缩放处理,若是须要解压缩,最后再解压缩一下。
指标
- 缓存有效期:经过
maxCacheAge
属性设置,默认一周- 缓存体积最大限制:经过
maxCacheSize
来设置的,默认为 0。
SDImageCache
在初始化时添加了通知观察者,因此在应用即将终止时和退到后台时,都会调用 -cleanDiskWithCompletionBlock:
方法来异步清扫缓存。 清扫磁盘缓存(clean): 遍历全部缓存文件,若是设置了 maxCacheAge
(最大缓存不过时时间) 属性的话,先删掉过时的文件,同时记录文件的属性和整体积大小,把文件按修改时间从早到晚排序,再遍历这个文件数组,一个一个删,直到整体积小于 desiredCacheSize 为止,也就是 maxCacheSize 的一半。
主要任务
具体实现: +initialize
中主要是经过注册通知 让SDNetworkActivityIndicator
监听下载事件,来显示和隐藏状态栏上的 network activity indicator。 为了让 SDNetworkActivityIndicator
文件能够不用导入项目中来(若是不要的话),这里使用了 runtime 的方式来实现动态建立类以及调用方法。
+ (void)initialize { if (NSClassFromString(@"SDNetworkActivityIndicator")) { id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")]; # 先移除通知观察者 SDNetworkActivityIndicator # 再添加通知观察者 SDNetworkActivityIndicator } } 复制代码
+sharedDownloader
方法中调用了 -init
方法来建立一个单例,-init
方法中作了一些初始化设置和默认值设置,包括设置最大并发数(6)、下载超时时长(15s)等。
核心方法: - downloadImageWithURL: options: progress: completed:
方法 首先经过调用 -addProgressCallback: andCompletedBlock: forURL: createCallback:
方法来保存每一个 url 对应的回调 block -addProgressCallback: ...
方法先进行错误检查,判断 URL 是否为空,而后再将 URL 对应的 progressBlock
和 completedBlock
保存到 URLCallbacks
属性中。
URLCallbacks
属性是一个 NSMutableDictionary
对象,key 是图片的 URL,value 是一个数组,包含每一个图片的多组回调信息。
由于可能同时下载多张图片,因此就可能出现多个线程同时访问 URLCallbacks
属性的状况。为了保证线程安全,因此这里使用了 dispatch_barrier_sync
来分步执行添加到 barrierQueue
中的任务,这样就能保证同一时间只有一个线程能对 URLCallbacks
进行操做。
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback { //1. 判断 url 是否为 nil,若是为 nil 则直接回调 completedBlock,返回失败的结果,而后 return,由于 url 会做为存储 callbacks 的 key //2. 处理同一个 URL 的屡次下载请求(MARK: 使用 dispatch_barrier_sync 函数来保证同一时间只有一个线程能对 URLCallbacks 进行操做): //3. 从属性 URLCallbacks(一个字典) 中取出对应 url 的 callBacksForURL(这是一个数组,由于可能一个 url 不止在一个地方下载) //4. 若是没有取到,也就意味着这个 url 是第一次下载,那就初始化一个 callBacksForURL 放到属性 URLCallbacks 中 //5. 往数组 callBacksForURL 中添加 包装有 callbacks(progressBlock 和 completedBlock)的字典 //6. 更新 URLCallbacks 存储的对应 url 的 callBacksForUR } 复制代码
若是这个 URL 是第一次被下载,就要回调 createCallback
,createCallback
主要作的就是建立并开启下载任务
createCallback
方法中调用了 - [SDWebImageDownloaderOperation initWithRequest: options: progress:]
方法来建立下载任务 SDWebImageDownloaderOperation
。
因为 UIImage 的 imageWithData
函数是每次画图的时候才将 Data 解压成 ARGB 的图像,因此在每次画图的时候都会有一个解压操做,这样虽然只有瞬时的内存需求,可是效率很低。 为了提升效率,经过 SDWebImageDecoder 将包装在 Data 下的资源画在另一张图片上,这样这张新图片就再也不须要重复解压了,是空间换时间的作法。
图片的解码实际是将图片的二进制数据转换成像素数据的过程,SD 对图片进行从新绘制,获得一张位图。 显示图片须要 RGBA 的色彩空间(什么是 RGBA ?),可是 PNG 和 JPEG 自身的格式非 RGBA。因此建立一个 BitmapImage,先在非UI线程渲染图片,做为预解码,而后拿到UIImage去显示。 iOS 图片解码
能够预先下载,可是下载是低优先级的。
State
operation 的执行过程: isReady -> isExecuting -> isFinished经过 keypath 的 KVO 通知来隐式的获得 state ,而不是显式的经过一个 state 的属性。当一个 operation 已经准备就绪,将要被执行时,它会为 isReadykeyPath 发送一个KVO的通知,对应的属性值也会变为YES.
为了构造一致的状态,每一个属性都与其余属性相互排斥: isReady : 若是 operation 已经作好了执行的准备返回YES,若是它所依赖的操做存在一些未完成的初始化步骤则返回NO。 isExecuting :若是 operation 正在执行它的任务返回YES,不然返回NO。 isFinished : 任务成功的完成了执行,或者中途被 Cancel ,返回YES。
NSOperationQueue 只会把 isFinished 为 YES 的 operation 踢出队列, isFinished 为 NO 的永远不会被移除,因此实现时要保证其正确性,避免死锁发生
Cancellation
取消一个 operation 的两种状况:
NSOperation 的被取消也是经过 isCancelledkeypath 的 KVO 来得到。当 NSOperation 的子类覆写 cancel 方法时,注意清理掉内部分配的资源。 特别注意的是,这时 isCancelled 和 isFinished 的值都变为了 YES, isExecuting 为值变为NO。
cancel : 带一个”l” 表示方法 (动词) isCancelled : 带两个”l”表示属性(形容词)
优先级 Priority
设置 queuePriority 属性就能够提高和下降 operation 的优先级, queuePriority 属性可选的值以下: NSOperationQueuePriorityVeryHigh NSOperationQueuePriorityHigh NSOperationQueuePriorityNormal NSOperationQueuePriorityLow NSOperationQueuePriorityVeryLow 另外,operation 能够指定一个 threadPriority 值,它的取值范围是0.0到1.0,1.0表明最高的优先级。 queuePriority:决定执行顺序的优先级 threadPriority:决定 operation 开始执行以后分配的计算资源的多少
依赖 Dependencies
若是须要把一个大的任务分红多个子任务,可使用依赖,来保证前后执行顺序。 B 操做若是依赖于 A,则必须在 A operation 的 isFinished 为 YES 的时候才会开始执行。 【避免循环依赖产生死锁】
[resizingOperation addDependency:networkingOperation];
[operationQueue addOperation:networkingOperation];
[operationQueue addOperation:resizingOperation];
复制代码
SDWebImageCombinedOperation 当 url 被正确传入以后, 会实例一个很是奇怪的 “operation”, 它实际上是一个遵循 SDWebImageOperation 协议的 NSObject 的子类. 而这个协议也很是的简单:
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end
复制代码
这里仅仅是将这个 SDWebImageOperation 类包装成一个看着像 NSOperation 其实并非 NSOperation 的类, 而这个类惟一与 NSOperation 的相同之处就是它们均可以响应 cancel 方法. (不知道这句看似像绕口令的话, 你看懂没有, 若是没看懂..请多读几遍). 而调用这个类的存在实际是为了使代码更加的简洁, 由于调用这个类的 cancel 方法, 会使得它持有的两个 operation 都被 cancel.
// SDWebImageCombinedOperation // cancel #1 - (void)cancel { self.cancelled = YES; if (self.cacheOperation) { [self.cacheOperation cancel]; self.cacheOperation = nil; } if (self.cancelBlock) { self.cancelBlock(); _cancelBlock = nil; } } 复制代码
而这个类, 应该是为了实现更简洁的 cancel 操做而设计出来的.
每张图片的下载都会发出一个异步的 HTTP 请求,由 DownloaderOperation
管理。
DownloaderOperation
继承 NSOperation
,遵照 SDWebImageOperation
、NSURLConnectionDataDelegate
协议。
SDWebImageOperation
协议只定义了一个方法 -cancel
,用来取消 operation。
当建立的 DownloaderOperation
对象被加入到 downloader
的 downloadQueue
中时,该对象的 -start
方法就会被自动调用。 -start
方法中首先建立了用来下载图片数据的 NSURLConnection
,而后开启 connection,同时发出开始图片下载的 当图片的全部数据下载完成后,Downloader
传入的 completionBlock
被调用,图片下载结束。
所以图片的数据下载是由一个 NSConnection
对象来完成的,这个对象的整个生命周期(从建立到下载结束)是由 DownloaderOperation
来控制的,将 operation
加入到 operation queue
中就能够实现多张图片同时下载了
NS_OPTIONS 枚举类型的使用 使用 NS_OPTIONS 位运算枚举类型,可同时 经过“与”运算符,能够判断是否设置了某个枚举选项,由于每一个枚举选择项中只有一位是1,其他位都是 0,因此只有参与运算的另外一个二进制值在一样的位置上也为 1,与 运算的结果才不会为 0. Eg. 0101 (至关于 SDWebImageDownloaderLowPriority | SDWebImageDownloaderUseNSURLCache) & 0100 (= 1 << 2,也就是 SDWebImageDownloaderUseNSURLCache) = 0100 (> 0,也就意味着 option 参数中设置了 SDWebImageDownloaderUseNSURLCache)
初始化 通常来讲,一个管理类都有一个全局的单例对象,根据业务需求设计不一样的初始化方法。在设计类的时候,应该经过合理的初始化方法告诉别的开发者,该类应该如何建立。
使用@synchronized: 在 Manager 对 failedURLs
和 runningOperations
作操做时均使用了@synchronized,在新版本里换成了 GCD 实现
下载高分辨率图,致使内存暴增的解决办法
FLAnimatedImage
来处理动图SDImageCacheConfig
对缓存进行配置,能够选择是否解压缓存、iCloud、最大缓存大小。sd_decompressedAndScaledDownImageWithImage:
避免要缩放的图片太大,采用的方式是将图片分割成一系列大小的小方块,而后每一个方块去获取 Image 并 draw 到目标 BitmapContext 上对于缓存如何作优化?
(1). -> LRU -> LRU+FIFO
(2). 缓存模糊匹配 针对同一图片,不一样大小的请求。若是缓存中有更大的图片,也视为命中缓存。