框架架构的主要目的是为了将项目模块化,分层,把代码解耦从而提高代码的复用
而 SDWebImage
异步下载并支持缓存,图片编码解码的框架
协调器 Manager
- Manager 使用中介者模式,用于管理协调各个子模块之间交互
- SDWebImageManager 依赖 SDWebImageOptions
(配置信息),各个子模块都是聚合关系 - 子模块都是协议!使用协议作为隔离层用于解耦 Manager 跟子模块,Manager 不需要知道具体的子模块到底是什么
- 聚合的关系,说明子模块可以独立于 Manager 而独立使用
cacheKey(for url: URL?) -> String?
- 使用 url 作为cache key,用于读写 image
子模块分析,先分析 imageLoader和 imageCache
子模块 SDImageLoader
SDImageLoader 协议
- SDImageLoader接口具体实现:
SDImageLoadersManager
组合模式,内部维护Array<SDImageLoader>
SDWebImageDownloader
具体image 下载逻辑实现
- SDImageLoader接口依赖
SDWebImageDownloaderOptions
,配置信息跟逻辑拆分,聚合配置信息,避免逻辑方法调用过程中配置参数零散
1 | public protocol SDImageLoader : NSObjectProtocol { |
Q:在想为什么协议只暴露三个接口??
一些思考
- 图片请求的并发量
- 请求超时策略
- 请求优先级
SDWebImageDownloaderOptions
位枚举,下载器功能自定义配置,用户可更改配置改变 Downloader 中的行为
1 | public struct SDWebImageDownloaderOptions : OptionSet { |
SDWebImageContext
根据字典拿到辅助功能对象
1 | typedef NSString * SDWebImageContextOption NS_EXTENSIBLE_STRING_ENUM; |
1 | SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey"; |
为什么使用字典而不是在创建一个类呢?
可能这些类or配置信息都不重要,使用字典管理
具体下载逻辑 SDWebImageDownloader
- 依赖
SDWebImageDownloadToken
(关联具体的 downloadOperation) - 依赖
SDWebImageDownloadOptions
- 依赖
SDWebImageDownloaderOperation
(内部封装 downloadTask,coderQueue) - 依赖
SDWebImageDownloaderConfig
(下载配置信息) - 聚合
SDWebImageDownloaderRequestModifier
(在请求前修改 request) - 聚合
SDWebImageDownloaderResponsetModifier
(在响应后修改 response) - 聚合
SDWebImageDownloaderDecryptor
- 聚合
NSURLSessionConfiguration
(下载operation 配置的 session 环境)
私有属性,维护下载任务
- URLSession
- OperationQueue,URLOperations
大致过程
- SDWebImageDownloader.init
- 配置 downloader config
- downloadQueue 并发 default 6 thread
- 配置 httpHeader
- 配置 urlSession
- url -> SDWebImageDownloadToken -> SDWebImageDownloaderOperation
- 配置 request
- 根据 context 配置 SDWebImageDownloaderOperation
- operation.start 中封装 dataTask
- 配置 coderQueue(serial) SDWebImageDownloaderOperation taskDelegate receive data 回调中边下载data 边解码
- dataTask 结束后调用 operation 的 completeBlock 将 imageData 交给用户
子模块 SDImageCache
内存:当前程序运行空间,空间小,数据易丢失,读取快
程序运行后,内存分为 5 个区:栈区,堆区,全局区,常量区,代码区
内存缓存设计一般使用:栈区,堆区
磁盘:空间大、可持久、读取速度慢
iOS 主要提供4种磁盘存储方式:
- NSKeyedArchiver:归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,如果想改动数据的某一小部分,需要解压整个数据或者归档整个数据。
- NSUserDefaults:用来保存应用程序设置和属性、用户保存的数据。存储的数据类型少,包括:NSData、NSString、NSNumber、NSDate、NSArray、 NSDictionary
- plist:userDefault就是一个 plist
- FileManager 相关 write 写入方式:永久保存在磁盘中
- 数据库
App 的沙盒根目录结构:
- Documents
- Library
- temp
大体结构,在网络层的 operation 中 coderQueue(serial) 负责解码 image
- 依赖 SDImageCacheOptions
- 聚合 SDMemoryCache
- 聚合 SDDiskCache
- 聚合 SDImageCacheConfig
- 使用组合模式 SDImageCachesManager 做分支节点,SDImageCache 做叶子节点
- SDImageCacheManager 依赖 SDImageCachesManagerOperationPolicy(用于处理多个 cache)
计算机组成原理:层级存储器设计思想
SDMemoryCache
内存管理设计
内存空间(因为是内存,使用什么数据结构?)
3个队列:
50 * 10 kb
20 * 100 kb
10 * 100+kb
队列淘汰策略
FIFO 算法
LRU 算法(检查时机)
内部使用NSCache 实现缓存
NSMapTable 作为弱引用缓存
SDDiskCache
磁盘管理设计
存储方式
空间大小限制
淘汰策略
根据文件属性删减文件
- (void)removeExpiredData
解码
- 使用策略模式针对不同图片解码
- 解码时机:
- 图片数据下载完成
- 磁盘读取后
图像渲染优化
优化的方式有很多,架构层次跨度上从底层的 Core Graphics、vImage、Image I/O 到上层的 Core Image 和 UIKit 都有。
- 绘制到 UIGraphicsImageRenderer 上
- 绘制到 Core Graphics Context 上
- 使用 Image I/O 创建缩略图像
- 使用 Core Image 进行 Lanczos 重采样
- 使用 vImage 优化图片渲染
绘制到 UIGraphicsImageRenderer 上
为了统一调用方式,以下的每种技术共用一个公共接口方法:
1 | func resizedImage(at url: URL, for size: CGSize) -> UIImage? { <#...#> } |
这里,size 的计量单位不是用 pixel,而是用 point。想要计算出你调整大小后图像的等效尺寸,用主 UIScreen 的 scale,等比例放大你 UIImageView 的 size 大小:
let scaleFactor = UIScreen.main.scale
let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
let size = imageView.bounds.size.applying(scale)