01-研究优秀开源框架@图层处理@iOS | SDWebImage 框架:从使用到原理解析

7 阅读31分钟

📋 目录


一、SDWebImage 概述与历史演进

0. 框架结构概览与功能简介

SDWebImage 的框架结构

SDWebImage的框架结构

SDWebImage 的图片下载分类,只要一行代码就可以实现图片异步下载和缓存功能。

功能简介

  1. 一个添加了 web 图片加载和缓存管理的 UIImageView 分类
  2. 一个异步图片下载器
  3. 一个异步的内存加磁盘综合存储图片并且自动处理过期图片
  4. 支持动态 gif 图
    • 4.0 之前的动图效果并不是太好
    • 4.0 以后基于 FLAnimatedImage 加载动图
  5. 支持 webP 格式的图片
  6. 后台图片解压处理
  7. 确保同样的图片 url 不会下载多次
  8. 确保伪造的图片 url 不会重复尝试下载
  9. 确保主线程不会阻塞

1. 框架简介

SDWebImage 是 Apple 平台(iOS / macOS / watchOS / visionOS)上广泛使用的异步图片下载与缓存库,提供从网络(或自定义 Loader)加载图片、解码、变换、缓存到展示的完整管线。其「图层处理」相关能力主要体现在:解码管线(将压缩数据解码为可渲染的位图)与变换管线(在解码后对位图做缩放、裁剪、滤镜等处理),二者共同构成「从数据到屏幕」的中间处理层。

2. 技术演进与版本脉络

SDWebImage 的图层处理能力并非一蹴而就,而是随版本逐步完善,与系统 API 和业界实践同步演进。

阶段版本/时期解码与图层处理相关能力
早期3.x 及以前以网络下载 + 简单缓存为主,解码依赖系统默认行为
规范化4.0引入 Custom Download Operation、更清晰的职责划分
编解码扩展4.2Custom Coder:支持注册自定义编解码器(如 WebP、渐进 JPEG)
统一管线5.0Image TransformerAnimated Image 全栈方案(GIF/WebP/APNG)、解码与变换在 Manager 内统一调度
精细化5.x 后续缩略图解码、HDR、强制解码策略(Force Decode Policy)、解码尺度与字节限制等

5.0 是重要分水岭:解码(Coder)、变换(Transformer)、缓存(Cache)、加载(Loader)在 SDWebImageManager 中形成一条清晰流水线,便于理解「图层处理」在整体中的位置。

3. 图层处理在整体架构中的位置

下图概括了从「URL 请求」到「显示到视图」的流程,并标出解码与变换所在阶段。

flowchart LR
    subgraph 输入
        A[URL / 自定义 Loader]
    end
    subgraph 加载
        B[SDImageLoader]
    end
    subgraph 解码层
        C[Data]
        D[SDImageCoder 解码]
        E[UIImage/NSImage]
    end
    subgraph 变换层
        F[SDImageTransformer]
        G[变换后图像]
    end
    subgraph 缓存与输出
        H[SDImageCache]
        I[UIImageView 等]
    end
    A --> B --> C --> D --> E --> F --> G --> H --> I

要点

  • 解码(Decoder):将压缩格式数据(JPEG/PNG/WebP/HEIC/AVIF 等)转为内存中的位图(如 UIImage/NSImage),是「数据 → 图层」的第一步。
  • 变换(Transformer):在「已解码的位图」上做几何或像素级处理(缩放、裁剪、圆角、滤镜等),输出仍是位图,再写入缓存或交给视图。
  • 二者均可在后台线程执行,避免阻塞主线程,符合 Apple 在 WWDC 等场合强调的「Image and Graphics Best Practices」[1]。

二、图像解码管线(Decoder Pipeline)

1. 解码的基础概念与双缓冲模型

在操作系统与图形栈中,图像通常以两种形式存在:

  1. 数据缓冲(Data Buffer)
    即磁盘或网络中的压缩编码数据(如 JPEG、PNG 的二进制)。体积小,但不能直接用于渲染。

  2. 图像缓冲(Image Buffer)
    解码后的像素矩阵(如 RGBA 位图),可被 GPU/CPU 渲染。其大小与分辨率(宽×高×通道数)成正比,与压缩格式无关。

因此,解码(Decoding) 的含义是:将 Data Buffer 转换为 Image Buffer。该过程是 CPU 密集型,且解码后的图像缓冲往往远大于原始数据(例如一张 4K 图片可解码为上百 MB 像素数据)。系统会在首次渲染时触发解码,若在主线程进行,易造成卡顿;若不经控制,大图会带来内存峰值与 OOM 风险。

双缓冲概念可归纳为

┌─────────────────┐     decode      ┌─────────────────┐
│  Data Buffer    │  ────────────►  │  Image Buffer   │
│ (JPEG/PNG/…)    │   (CPU 密集)     │  (像素矩阵)      │
└─────────────────┘                 └─────────────────┘
      体积较小                          体积 ∝ 宽×高×4

Apple 在 WWDC 2018「Image and Graphics Best Practices」[1] 中明确指出:解码后的缓冲区大小由图像尺寸决定,而非显示尺寸;因此在解码阶段就做下采样(Downsampling),避免先解码全尺寸再缩放的巨大内存与 CPU 开销。

2. 缩略图与下采样(Downsampling)

下采样指在解码时直接生成较小尺寸的位图,而不是先解码全图再缩放。这样既能减少内存占用,也能减少解码与后续绘制的计算量。

2.1 系统 API:ImageIO 与缩略图

在 iOS/macOS 上,推荐使用 ImageIOCGImageSourceCreateThumbnailAtIndex 在解码阶段就限制最大尺寸,从而在内存中只生成缩略图级别的像素缓冲 [2][3]。

算法思路(伪代码)

函数 DownsampleImage(数据 data, 最大边长 maxPixelSize):
    1. 使用 data 创建 CGImageSourceRef source
    2. 设置选项 options:
       - kCGImageSourceCreateThumbnailFromImageAlways: true
       - kCGImageSourceCreateThumbnailWithTransform: true
       - kCGImageSourceThumbnailMaxPixelSize: maxPixelSize
    3. thumbnail = CGImageSourceCreateThumbnailAtIndex(source, 0, options)
    4. 由 thumbnail 创建 UIImage/NSImage 并返回

这样,解码器内部可以在部分解码低分辨率解码路径上生成缩略图,避免全图解码。SDWebImage 在 5.x 中通过 SDImageCoderDecodeScaleDownLimitBytes 等能力,将「按目标尺寸或字节限制做缩略图解码」纳入其解码管线,与上述思路一致。

2.2 下采样算法与内存估算

算法步骤(与 ImageIO 语义一致)

  1. Data 创建 CGImageSourceRef,不立即解码全图。
  2. 从 source 读取图像属性(宽、高),计算缩放比例,使长边不超过 maxPixelSize
  3. 设置 kCGImageSourceThumbnailMaxPixelSizekCGImageSourceCreateThumbnailFromImageAlwayskCGImageSourceCreateThumbnailWithTransform 等选项。
  4. 调用 CGImageSourceCreateThumbnailAtIndex(source, 0, options) 得到缩略图 CGImage
  5. CGImage 创建 UIImage/NSImage 并返回。

这样解码器在内部只生成目标尺寸的像素缓冲,避免「先全图解码再缩放」的双倍内存与 CPU 开销。

2.3 内存与性能关系

下采样带来的内存节省可近似表示为:

  • 全图解码:memory ≈ width × height × 4(假设 RGBA)。
  • 限制最大边长 L 后:若等比缩放,则 memory ≈ L² × 4,与原始分辨率无关。

因此,在列表、缩略图等场景下,在解码阶段就限制最大尺寸是业界公认的最佳实践,也是 SDWebImage 解码管线优化的核心之一。

3. 渐进式解码(Progressive Decoding)

渐进式编码(如 Progressive JPEG)允许数据分块到达时逐步呈现:先看到模糊全图,再随数据增多逐步变清晰。渐进式解码即在未完整接收数据时,对当前已有数据做解码并显示,以提升感知性能(尤其在弱网环境)[4]。

流程概念

sequenceDiagram
    participant N as 网络
    participant D as 渐进式解码器
    participant V as 视图

    N->>D: 数据块 1
    D->>D: 解码当前数据
    D->>V: 显示低分辨率帧 1
    N->>D: 数据块 2
    D->>D: 更新解码状态,解码
    D->>V: 显示更清晰帧 2
    N->>D: 数据块 n(完成)
    D->>D: 最终解码
    D->>V: 显示最终图像

SDWebImage 通过 SDWebImageProgressiveCoder 协议扩展解码器:支持「增量数据」输入,每次 updateIncrementalData:finished: 时更新内部解码状态并输出当前可用的图像,供上层展示。对动图(如 GIF),还可配合 SDAnimatedImageCoder 在渐进加载时逐帧解码并驱动 SDAnimatedImageView 的渐进动画。

渐进式解码流程(伪代码)

函数 ProgressiveDecode:
    状态: 已接收数据 buffer, 解码器内部状态 decoderState
    当 收到新数据块 chunk:
        append(buffer, chunk)
        decodedFrame = decoder.decodeIncremental(buffer, decoderState)
        若 decodedFrame 非空:
            回调 onPartialImage(decodedFrame)
    当 数据接收完成:
        finalImage = decoder.finalize(buffer, decoderState)
        回调 onComplete(finalImage)

注意:渐进式解码比单次完整解码的 CPU 开销更高 [4],适合「先显示再细化」的体验需求,需在流畅度与电量之间做权衡。

4. 编解码器扩展与多格式支持

SDWebImage 将「解码 / 编码」抽象为 SDImageCoder 协议,通过 SDImageCodersManager 注册多个 Coder,按数据格式(或 MIME 类型)选择对应实现。这样可在不修改核心管线的前提下支持新格式。

解码器选择与解码流程(高层)

flowchart TD
    A[Image Data] --> B{SDImageCodersManager}
    B --> C[遍历已注册 Coder]
    C --> D{canDecodeFromData?}
    D -->|是| E[该 Coder 解码]
    D -->|否| C
    E --> F[UIImage/NSImage]
    F --> G[可选: 缩略图/字节限制]
    G --> H[解码结果]

典型 Coder 职责

方法/能力含义
decodedImageWithData:options:将 Data 解码为 UIImage/NSImage
encodedDataWithImage:format:options:将图像编码为指定格式 Data
canDecodeFromData: / canEncodeToFormat:是否支持某格式的解码/编码

动图则通过 SDAnimatedImageCoder 扩展:提供按帧解码、帧时长、循环次数等,供 SDAnimatedImage + SDAnimatedImageView 使用。内置支持 GIF、WebP、APNG、HEIC 动图等;用户也可实现自定义 Coder 并注册,从而纳入统一的加载与缓存流程。


三、图像变换管线(Transformer Pipeline)

1. 变换器的设计思想与协议

图像变换在 SDWebImage 中定义为:输入与输出均为图像对象(如 UIImage/NSImage)的运算。与 Coder(Data ↔ Image)不同,Transformer 只做 Image → Image,例如缩放、裁剪、圆角、滤镜等,对应「数字图像处理」中的几何变换与像素操作 [5]。

协议设计(概念)

协议 SDImageTransformer:
    方法 transform(image, key) -> Image?:
        输入: 原始图像、缓存 key(可选,用于生成变换后的 cache key)
        输出: 变换后的图像;失败可返回 nil

这样设计便于:

  • SDWebImageManager 中,在「解码完成」之后、「写入缓存」之前插入变换步骤;
  • 对同一 URL 可因不同变换参数得到不同 cache key,从而分别缓存原图与变换结果。

2. 内置变换器与组合管线

SDWebImage 提供多种内置 Transformer,覆盖常见 UI 需求:

变换器功能说明
SDImageResizingTransformer缩放到指定尺寸,支持 scaleMode(fill/aspectFit 等)
SDImageCroppingTransformer按矩形裁剪
SDImageRoundCornerTransformer圆角(可带边框)
SDImageRotationTransformer按角度旋转,可选 fitSize
SDImageFlippingTransformer水平/垂直翻转
SDImageBlurTransformer高斯模糊
SDImageTintTransformer颜色 tint
SDImageFilterTransformer基于 CIFilter 的滤镜(除 watchOS 外)

组合管线:通过 SDImagePipelineTransformer 将多个 Transformer 按顺序组合,形成链式处理:

图像 → Transformer1 → Transformer2 → … → TransformerN → 最终图像

例如先裁剪再圆角再缩放,只需将三个 Transformer 放入一个 Pipeline 即可。对应伪代码:

pipeline = SDImagePipelineTransformer([CropTransformer(rect), RoundCornerTransformer(radius), ResizingTransformer(size)])
resultImage = pipeline.transform(originalImage, key)

在 Swift/Objective-C 中的用法可参见官方 Advanced Usage - Image Transformer [6]。

3. 变换与缓存的协同

变换发生在 Manager 层:
先由 Loader 得到 Data,由 Coder 解码得到 Image,再经 Transformer 得到最终 Image,最后再写入 Cache 并交给 UI。因此:

  • 原始图变换后的图可以分别缓存:
    • 原图可用 SDWebImageContextOriginalImageCache 指定单独缓存实例;
    • 变换后的图使用默认(或指定)的 Cache,其 cache key 会包含变换信息,避免不同变换结果互相覆盖。
  • 若只关心「下载 + 变换」而不写缓存,可通过 .fromLoaderOnlystoreCacheType = .none 实现,仅走 Loader → 解码 → 变换 → 回调,不读/写缓存。

变换与缓存的整体管线(含解码)

flowchart LR
    subgraph 请求
        U[URL + Context]
    end
    subgraph 缓存查询
        C1{查 Cache}
    end
    subgraph 加载与解码
        L[Loader]
        D[Coder 解码]
    end
    subgraph 变换
        T[Transformer]
    end
    subgraph 写回与展示
        C2[写 Cache]
        V[View]
    end
    U --> C1
    C1 -->|命中| V
    C1 -->|未命中| L --> D --> T --> C2 --> V

4. 应用场景简述

场景解码侧变换侧
列表缩略图使用 scaleDown/limitBytes 做缩略图解码,降低内存可选 ResizingTransformer 统一尺寸
头像/圆角常规解码即可RoundCornerTransformer
弱网/大图Progressive Coder 渐进显示可配合 Resizing 限制最终尺寸
相册/大图预览原图或高分辨率解码少用或仅做旋转/裁剪
动图(GIF/WebP)SDAnimatedImageCoder + 帧缓冲一般不做几何变换,或仅对首帧做

四、类结构图分析

1. 核心类总览

SDWebImage 的类可按职责分为:入口与协调加载缓存解码变换视图扩展 六类。下表给出核心类及其职责(名称以 5.x 为主,OC/Swift 可能略有差异)。

模块核心类 / 协议职责简述
协调SDWebImageManager统一入口:协调 Loader、Cache、Coder、Transformer,执行「查缓存 → 下载 → 解码 → 变换 → 写缓存」
加载SDImageLoader (协议)定义加载接口:根据 URL 返回 Data 或 Image
SDWebImageDownloader默认 Loader 实现:基于 URLSession 下载,支持并发、取消、RequestModifier
SDWebImageDownloaderOperation单次下载任务,实现 SDWebImageDownloaderOperation 协议
缓存SDImageCache内存 + 磁盘二级缓存,提供 query/store/remove,支持自定义 key、过期策略
SDMemoryCache / SDDiskCache内存层、磁盘层具体实现(5.x 可拆分)
解码SDImageCoder (协议)定义 Data ↔ Image 编解码,如 decodedImageWithData:options:
SDImageCodersManager管理多个 Coder,按数据格式选择可用 Coder
SDWebImageImageIOCoder内置 Coder 实现(JPEG/PNG/HEIC/…)
变换SDImageTransformer (协议)定义 Image → Image 变换,如 transformedImageWithImage:forKey:
SDImagePipelineTransformer将多个 Transformer 串联为一条管线
SDImageResizingTransformer内置 Transformer 实现
视图UIImageView+WebCache为 UIImageView 提供 sd_setImage(with:...)sd_cancelCurrentImageLoad
SDAnimatedImageView动图展示,配合 SDAnimatedImage
UIButton+WebCache其他控件的扩展

2. 模块划分与依赖关系

下图从「模块」维度表示各层之间的依赖方向:视图扩展依赖 Manager,Manager 依赖 Loader/Cache,解码与变换在 Manager 内被调用,Loader 只产出 Data,Cache 只做存取。

flowchart TB
    subgraph 视图层
        V1[UIImageView+WebCache]
        V2[UIButton+WebCache]
        V3[SDAnimatedImageView]
    end
    subgraph 协调层
        M[SDWebImageManager]
    end
    subgraph 加载层
        L[SDWebImageDownloader]
    end
    subgraph 缓存层
        C[SDImageCache]
    end
    subgraph 编解码层
        CM[SDImageCodersManager]
        CO[SDImageCoder 实现]
    end
    subgraph 变换层
        T[SDImageTransformer 实现]
    end
    V1 --> M
    V2 --> M
    V3 --> M
    M --> L
    M --> C
    M --> CM
    M --> T
    CM --> CO

3. 加载与缓存类结构

Loader 负责从网络(或自定义来源)获取数据;Cache 负责内存与磁盘的读写。Manager 持有两者引用,在单次请求中先问 Cache,未命中再调 Loader。

classDiagram
    class SDWebImageManager {
        -imageLoader: SDImageLoader
        -imageCache: SDImageCache
        +loadImage(with:options:context:progress:completed:)
        -callLoadImage(with:options:context:progress:completed:)
    }
    class SDImageLoader {
        <<protocol>>
        +requestImageWithURL:options:context:progress:completed()
        +canRequestImageForURL()
    }
    class SDWebImageDownloader {
        -session: URLSession
        -downloadQueue: NSOperationQueue
        +downloadImageWithURL:options:progress:completed()
    }
    class SDImageCache {
        -memoryCache: SDMemoryCache
        -diskCache: SDDiskCache
        +queryImageForKey:options:context:callback()
        +storeImage:imageData:forKey:completion()
        +removeImageForKey:withCompletion()
    }
    SDWebImageManager --> SDImageLoader : 使用
    SDWebImageManager --> SDImageCache : 使用
    SDWebImageDownloader ..|> SDImageLoader : 实现
  • SDWebImageManager:对外提供 loadImage(with:...),内部先查 imageCache,再根据需要调用 imageLoader,最后根据 context 决定是否解码、变换并写回缓存。
  • SDWebImageDownloader:实现 SDImageLoader 协议,通过 URLSession 下载,支持并发数、超时、RequestModifier;单次下载封装为 SDWebImageDownloaderOperation
  • SDImageCache:内存缓存通常用 NSCache 或自研 LRU,磁盘缓存为文件系统;query/store 的 key 由 Manager 根据 URL + context(含 transformer 等)生成。

4. 解码与变换类结构

Coder 将 Data 转为 Image(或反向);Transformer 将 Image 转为另一 Image。Manager 在「Loader 返回 Data 后」先选 Coder 解码,再按 context 中的 Transformer 做变换,得到最终 Image 再写入 Cache。

classDiagram
    class SDWebImageManager {
        -loadImage(with:...)
    }
    class SDImageCoder {
        <<protocol>>
        +decodedImageWithData:options()
        +encodedDataWithImage:format:options()
        +canDecodeFromData()
        +canEncodeToFormat()
    }
    class SDImageCodersManager {
        -coders: [SDImageCoder]
        +addCoder()
        +removeCoder()
        +canDecodeFromData()
        +decodedImageWithData:options()
    }
    class SDImageTransformer {
        <<protocol>>
        +transformerKey
        +transformedImageWithImage:forKey()
    }
    class SDImagePipelineTransformer {
        -transformers: [SDImageTransformer]
        +transformerKey
        +transformedImageWithImage:forKey()
    }
    SDWebImageManager ..> SDImageCodersManager : 解码时使用
    SDWebImageManager ..> SDImageTransformer : 变换时使用
    SDImageCodersManager --> SDImageCoder : 委托具体 Coder
    SDImagePipelineTransformer ..|> SDImageTransformer : 实现
  • SDImageCodersManager:持有一组 SDImageCoder,按 canDecodeFromData: 选出第一个能处理当前 Data 的 Coder 执行解码;编码同理。
  • SDImagePipelineTransformer:持有一组 SDImageTransformer,按顺序对 Image 依次变换;其 transformerKey 通常由各子 Transformer 的 key 拼接而成,参与缓存 key 生成。

5. View 扩展与调用链

视图扩展(如 UIImageView+WebCache)是业务最常接触的入口:内部将「当前 URL、placeholder、options、context」交给 SDWebImageManager,并把返回的加载任务与 view 关联,以便在复用时取消。

sequenceDiagram
    participant V as UIImageView
    participant Ext as UIImageView+WebCache
    participant M as SDWebImageManager
    participant C as SDImageCache
    participant L as SDWebImageDownloader

    V->>Ext: sd_setImage(with: url, ...)
    Ext->>Ext: sd_cancelCurrentImageLoad()
    Ext->>M: loadImage(with: url, context: [...])
    M->>C: queryImage(forKey:)
    alt 缓存命中
        C-->>M: image
        M-->>Ext: completed(image, .memory/.disk)
    else 未命中
        M->>L: requestImageWithURL:...
        L-->>M: data
        M->>M: 解码 + 变换
        M->>C: storeImage(forKey:)
        M-->>Ext: completed(image, .none)
    end
    Ext->>V: imageView.image = image
  • sd_setImage(with: placeholder: options: context: completed:):先对当前 view 取消未完成任务,再调 SDWebImageManager.shared.loadImage(with: url, options: options, context: context, progress: progress, completed: completed);在 completed 中把得到的 image 赋给 imageView.image(并可选执行 transition 动画)。
  • sd_cancelCurrentImageLoad():取消与该 view 绑定的 load 任务,避免 cell 复用时旧请求覆盖新图片。

将上述「核心类总览」「模块依赖」「Loader/Cache 类图」「Coder/Transformer 类图」「View 调用链」串联起来,即可形成对 SDWebImage 类结构图 的完整分析:入口在视图扩展,核心协调在 Manager,加载与缓存、解码与变换均为可插拔的协议实现,便于扩展与测试。


五、与系统及业界实践的衔接

1. Apple 图像与图形最佳实践

Apple 在 WWDC 2018「Image and Graphics Best Practices」[1] 中强调:

  • 在后台线程进行解码与下采样,避免在主线程做重 CPU 工作导致的卡顿。
  • 解码时即做下采样,使解码后的图像缓冲与显示尺寸匹配,降低内存与 CPU。
  • 预取(Prefetch):在列表等场景提前准备即将显示的图像,避免在滚动时才开始解码。

SDWebImage 的解码与变换均在后台队列执行,且支持按尺寸/字节限制的缩略图解码,与上述建议一致。其 Prefetch 能力(如 UITableView 的 prefetch 结合 sd_setImageWithURL:)可在业务层配合使用,实现「提前解码、避免滚动时卡顿」。

Force Decode 策略(5.17+):SDWebImage 引入 SDImageForceDecodePolicy,用于控制是否在加载管线中强制解码(将延迟解码的图片提前转为位图)。在部分场景下可避免在渲染阶段才触发 CA 的帧缓冲拷贝,从而降低主线程峰值与内存抖动;具体策略可根据「是否使用自定义渲染」「是否配合 Transformer」等选择,详见官方文档与 CHANGELOG。

2. 移动端图像管线研究简述

在移动端部署图像管线(含解码、缩放、轻量级「变换」)方面,业界与学界有大量工作:

  • FlexiViT [7] 等通过可变的 patch 尺寸在训练与推理时平衡精度与速度;
  • NanoFLUX [8]、SnapGen [9] 等关注在移动设备上的高效图像生成与压缩。
    这些工作与「在端侧做高效解码与分辨率控制」的目标一致:在有限算力与内存下,通过解码阶段控制(如缩略图、渐进解码)和管线化处理(解码 → 变换 → 缓存)提升体验。SDWebImage 的 Decoder + Transformer 双管线正是这一思路在「图片加载库」中的具体实现。

六、使用案例与原理分析

0. 框架结构速览

0.1 实现原理

  1. 架构图(UML 类图)

架构图(UML 类图)

  1. 流程图(方法调用顺序图)

1559217862563-364c0d60-3f2a-4db9-b5c5-e81f01cd125e.png

0.2 目录结构

  • Downloader\
    • SDWebImageDownloader\
    • SDWebImageDownloaderOperation
  • Cache\
    • SDImageCache
  • Utils\
    • SDWebImageManager\
    • SDWebImageDecoder\
    • SDWebImagePrefetcher
  • Categories\
    • UIView+WebCacheOperation\
    • UIImageView+WebCache\
    • UIImageView+HighlightedWebCache\
    • UIButton+WebCache\
    • MKAnnotationView+WebCache\
    • NSData+ImageContentType\
    • UIImage+GIF\
    • UIImage+MultiFormat\
    • UIImage+WebP
  • Other\
    • SDWebImageOperation(协议)\
    • SDWebImageCompat(宏定义、常量、通用函数)

0.3 相关类名与功能描述

  • SDWebImageDownloader:是专门用来下载图片和优化图片加载的,跟缓存没有关系
  • SDWebImageDownloaderOperation:继承于 NSOperation,用来处理下载任务的
  • SDImageCache:用来处理内存缓存和磁盘缓存(可选)的,其中磁盘缓存是异步进行的,因此不会阻塞主线程
  • SDWebImageManager:作为 UIImageView+WebCache 背后的默默付出者,主要功能是将图片下载(SDWebImageDownloader)和图片缓存(SDImageCache)两个独立的功能组合起来
  • SDWebImageDecoder:图片解码器,用于图片下载完成后进行解码
  • SDWebImagePrefetcher:预下载图片,方便后续使用,图片下载的优先级低,其内部由 SDWebImageManager 来处理图片下载和缓存
  • UIView+WebCacheOperation:用来记录图片加载的 operation,方便需要时取消和移除图片加载的 operation
  • UIImageView+WebCache:集成 SDWebImageManager 的图片下载和缓存功能到 UIImageView 的方法中,方便调用方的简单使用
  • UIImageView+HighlightedWebCache:跟 UIImageView+WebCache 类似,也是包装了 SDWebImageManager,只不过是用于加载 highlighted 状态的图片
  • UIButton+WebCache:跟 UIImageView+WebCache 类似,集成 SDWebImageManager 的图片下载和缓存功能到 UIButton 的方法中,方便调用方的简单使用
  • MKAnnotationView+WebCache:跟 UIImageView+WebCache 类似
  • NSData+ImageContentType:用于获取图片数据的格式(JPEG、PNG 等)
  • UIImage+GIF:用于加载 GIF 动图
  • UIImage+MultiFormat:根据不同格式的二进制数据转成 UIImage 对象
  • UIImage+WebP:用于解码并加载 WebP 图片

0.4 工作流程

工作流程

  • 入口 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(3.8.0 之后使用了 NSURLSession),实现相关 delegate 来判断图片下载中、下载完成和下载失败。
  • connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  • 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  • 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
  • imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  • 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  • 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
  • SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
  • SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

1. 典型使用案例

1.1 列表 Cell 中加载缩略图(防错位 + 下采样)

UITableView / UICollectionView 的 cell 中,若不限制图片尺寸,大图会带来内存峰值与卡顿;且 cell 复用时需避免「先显示旧图再被新图覆盖」的错位。SDWebImage 通过 URL 绑定取消机制 解决错位,通过 Transformer 限制尺寸 控制内存。

// Cell 内
func configure(with url: URL) {
    imageView.sd_cancelCurrentImageLoad()
    let transformer = SDImageResizingTransformer(
        size: CGSize(width: 120, height: 120),
        scaleMode: .aspectFill
    )
    imageView.sd_setImage(
        with: url,
        placeholderImage: UIImage(named: "placeholder"),
        context: [.imageTransformer: transformer]
    )
}

要点sd_cancelCurrentImageLoad() 会取消该 view 上未完成的请求,新 URL 加载完成后才设置,避免复用时显示错误图片。

1.2 预取(Prefetch)提前解码

利用系统预取 API 在 cell 尚未显示时就开始加载,滚动时直接从缓存读取,减少卡顿。

// 实现 UICollectionViewDataSourcePrefetching
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
    let urls = indexPaths.compactMap { model(at: $0).imageURL }
    urls.forEach { url in
        SDWebImagePrefetcher.shared.prefetchURLs([url])
    }
}

// 可选:取消不再需要的预取
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
    let urls = indexPaths.compactMap { model(at: $0).imageURL }
    SDWebImagePrefetcher.shared.cancelPrefetching(for: urls)
}

1.3 多设备/多 Channel 下的加载

在多设备场景(如同一 URL 在不同 Channel 下需要不同尺寸)中,通过 context 传入不同的 Transformer 或 Cache,使同一 URL 对应多条缓存条目。

// 列表用小图
imageView.sd_setImage(with: url, context: [
    .imageTransformer: SDImageResizingTransformer(size: CGSize(width: 80, height: 80), scaleMode: .aspectFill)
])

// 详情用原图或大图
detailImageView.sd_setImage(with: url)  // 不传 transformer,用原图

1.4 占位图 + 加载完成过渡动画

通过 sd_imageTransition 在图片从网络加载完成后做淡入等过渡,提升观感。

imageView.sd_imageTransition = .fade(0.25)
imageView.sd_setImage(with: url, placeholderImage: placeholder)

1.5 仅下载不展示(后台缓存)

希望提前把图片下载并写入缓存,供后续使用,而不绑定到某个 view。

SDWebImageManager.shared.loadImage(
    with: url,
    options: [],
    progress: nil
) { image, data, error, cacheType, finished, url in
    if let image = image, finished {
        // 已缓存,可做后续逻辑
    }
}

1.6 完成回调与错误处理

通过 completed 区分来源(内存/磁盘/网络)并处理失败与取消。

imageView.sd_setImage(with: url, placeholderImage: placeholder) { image, error, cacheType, url in
    if let error = error {
        // 可根据 error 类型提示用户或降级
        return
    }
    switch cacheType {
    case .none:   break // 本次从网络加载
    case .memory: break // 从内存缓存
    case .disk:   break // 从磁盘缓存
    @unknown default: break
    }
}

2. 更多使用案例与代码

2.1 UITableViewCell 完整示例(含复用与尺寸)

class PhotoCell: UITableViewCell {
    static let reuseId = "PhotoCell"
    @IBOutlet weak var photoImageView: UIImageView!
    @IBOutlet weak var progressView: UIProgressView!

    override func prepareForReuse() {
        super.prepareForReuse()
        photoImageView.sd_cancelCurrentImageLoad()
        photoImageView.image = nil
        progressView.progress = 0
    }

    func configure(with url: URL) {
        let size = photoImageView.bounds.size
        let transformer = SDImageResizingTransformer(
            size: size.isEmpty ? CGSize(width: 120, height: 120) : size,
            scaleMode: .aspectFill
        )
        photoImageView.sd_setImage(
            with: url,
            placeholderImage: UIImage(named: "placeholder"),
            context: [.imageTransformer: transformer],
            progress: { [weak self] received, total, _ in
                guard let self = self, total > 0 else { return }
                DispatchQueue.main.async {
                    self.progressView.progress = Float(received) / Float(total)
                }
            },
            completed: { [weak self] image, error, _, _ in
                DispatchQueue.main.async {
                    self?.progressView.isHidden = (image != nil)
                }
            }
        )
    }
}

2.2 UIButton 设置网络图片

// 设置不同 state 的图片
button.sd_setImage(with: url, for: .normal, placeholderImage: UIImage(named: "btn_placeholder"))
button.sd_setImage(with: highlightedURL, for: .highlighted)
button.sd_setBackgroundImage(with: backgroundURL, for: .normal)

// 带圆角与完成回调
let transformer = SDImageRoundCornerTransformer(radius: 8, corners: .allCorners, borderWidth: 0, borderColor: nil)
button.sd_setImage(with: url, for: .normal, placeholderImage: nil, context: [.imageTransformer: transformer]) { _, error, _, _ in
    if error != nil { print("加载失败") }
}

2.3 自定义缓存键(同一 URL 多用途)

当同一 URL 在不同业务下需要不同缓存(例如列表用缩略图、详情用原图)时,可用 cacheKeyFilter 或自定义 key。

// 方式一:通过 context 的 cacheKeyFilter 生成不同 key
let listKeyFilter: SDWebImageCacheKeyFilter = { url in
    return "list_\(url?.absoluteString ?? "")" as NSString
}
imageView.sd_setImage(with: url, context: [.cacheKeyFilter: listKeyFilter])

let detailKeyFilter: SDWebImageCacheKeyFilter = { url in
    return "detail_\(url?.absoluteString ?? "")" as NSString
}
detailImageView.sd_setImage(with: url, context: [.cacheKeyFilter: detailKeyFilter])

// 方式二:在业务层用不同 URL 或 query 区分(如服务端支持 ?size=thumb)
let listURL = url.appendingPathComponent("?size=thumb")
let detailURL = url
imageView.sd_setImage(with: listURL, context: [.cacheKeyFilter: listKeyFilter])
detailImageView.sd_setImage(with: detailURL, context: [.cacheKeyFilter: detailKeyFilter])

2.4 请求修饰(Header、Token、超时)

需要带鉴权或自定义 Header 时,使用 requestModifier

let modifier = SDWebImageDownloaderRequestModifier { request in
    var r = request
    r.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    r.setValue("image/webp,image/*,*/*;q=0.8", forHTTPHeaderField: "Accept")
    r.timeoutInterval = 30
    return r
}
imageView.sd_setImage(
    with: url,
    context: [.requestModifier: modifier]
)

2.5 下载进度条 + 占位 + 过渡动画

imageView.sd_imageTransition = .fade(0.3)
imageView.sd_setImage(
    with: url,
    placeholderImage: UIImage(named: "placeholder"),
    options: [.retryFailed],
    progress: { [weak progressView] received, total, _ in
        guard let pv = progressView, total > 0 else { return }
        DispatchQueue.main.async {
            pv.progress = Float(received) / Float(total)
            pv.isHidden = false
        }
    },
    completed: { [weak progressView] image, _, cacheType, _ in
        DispatchQueue.main.async {
            progressView?.isHidden = true
            if image == nil { /* 显示失败占位 */ }
        }
    }
)

2.6 动图 GIF(SDAnimatedImageView)

// 使用 SDAnimatedImageView 播放 GIF/WebP/APNG
let animatedImageView = SDAnimatedImageView()
animatedImageView.sd_setImage(with: gifURL, placeholderImage: nil)

// 仅加载动图第一帧作为封面(节省内存)
animatedImageView.sd_setImage(with: gifURL, placeholderImage: nil, options: [.decodeFirstFrameOnly])

// 渐进式加载动图(边下边播)
animatedImageView.sd_setImage(with: gifURL, placeholderImage: nil, options: [.progressiveLoad])

2.7 缓存查询与手动存储

let cache = SDImageCache.shared
let key = url.absoluteString

// 查询是否已缓存
cache.containsImage(forKey: key) { cacheType in
    switch cacheType {
    case .none:   print("未缓存")
    case .memory: print("在内存")
    case .disk:   print("在磁盘")
    @unknown default: break
    }
}

// 从缓存读取(不触发下载)
cache.queryImage(forKey: key, options: nil, context: nil) { image, data, cacheType in
    if let image = image {
        imageView.image = image
    }
}

// 手动写入缓存(例如本地生成或从相册来的图)
cache.store(image, forKey: key, completion: nil)

2.8 自定义 Transformer 示例

实现 SDImageTransformer 协议,对已解码图像做自定义绘制或滤镜(以下方法名以 SDWebImage 5.x 协议为准,实际请参照当前版本头文件)。

// 实现协议:为图片加灰色边框
class GrayBorderTransformer: NSObject, SDImageTransformer {
    var transformerKey: String { "GrayBorder(\(borderWidth))" }
    let borderWidth: CGFloat

    init(borderWidth: CGFloat = 2) { self.borderWidth = borderWidth }

    func transformedImage(with image: UIImage, forKey key: String) -> UIImage? {
        let size = image.size
        UIGraphicsBeginImageContextWithOptions(size, false, image.scale)
        defer { UIGraphicsEndImageContext() }
        image.draw(at: .zero)
        UIColor.gray.setStroke()
        let path = UIBezierPath(rect: CGRect(origin: .zero, size: size).insetBy(dx: borderWidth/2, dy: borderWidth/2))
        path.lineWidth = borderWidth
        path.stroke()
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

// 使用
let transformer = GrayBorderTransformer(borderWidth: 3)
imageView.sd_setImage(with: url, context: [.imageTransformer: transformer])

2.9 强制刷新与仅从缓存读取

// 忽略缓存,强制重新下载(适用于需要刷新内容的场景)
imageView.sd_setImage(with: url, options: [.forceRefresh])

// 仅从缓存读取,没有则显示占位或报错(离线/省流量场景)
imageView.sd_setImage(with: url, options: [.onlyFromCache]) { image, error, _, _ in
    if image == nil { print("缓存中无此图") }
}

2.10 Objective-C 常用写法

// 基础加载
[imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"placeholder"]];

// 带 context 的 Transformer
id<SDImageTransformer> transformer = [SDImagePipelineTransformer transformerWithTransformers:@[
    [SDImageResizingTransformer transformerWithSize:CGSizeMake(100, 100) scaleMode:SDImageScaleModeFill],
    [SDImageRoundCornerTransformer transformerWithRadius:10 corners:SDRectCornerAllCorners borderWidth:0 borderColor:nil]
]];
[imageView sd_setImageWithURL:url placeholderImage:nil context:@{SDWebImageContextImageTransformer: transformer}];

// 取消当前加载
[imageView sd_cancelCurrentImageLoad];

// 完成回调
[imageView sd_setImageWithURL:url completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
    if (error) { NSLog(@"加载失败: %@", error); }
}];

3. 核心流程原理分析

3.1 Manager 协调的完整链路

SDWebImageManager 是整条「加载 → 解码 → 变换 → 缓存」的协调者,其内部逻辑可概括为:

flowchart TD
    A[loadImageWithURL] --> B{查缓存 key}
    B --> C[先查内存]
    C --> D{命中?}
    D -->|是| E[回调 .memory]
    D -->|否| F[再查磁盘]
    F --> G{命中?}
    G -->|是| H[解码/反序列化]
    H --> I[写回内存]
    I --> E
    G -->|否| J[构造 Loader 任务]
    J --> K[Loader 返回 Data]
    K --> L[Coder 解码]
    L --> M{有 Transformer?}
    M -->|是| N[Transformer 变换]
    M -->|否| O[得到 Image]
    N --> O
    O --> P[写内存+磁盘缓存]
    P --> Q[回调 .none 或 .disk]

要点

  • 缓存 key 由 URL(或自定义 key)与 context(如 transformer、cacheKeyFilter)共同决定,同一 URL 不同 transformer 会得到不同 key。
  • 解码与变换均在 Manager 持有的串行/并发队列 中执行,回调通过 dispatch_async(main_queue) 回到主线程,便于更新 UI。

3.2 回调与线程模型

  • progress:在下载进度回调所在线程(多为 URLSession 回调线程),若需更新 UI 需自行切主线程。
  • completed:SDWebImage 内部会派发到主线程再调用,因此 completed 里可直接操作 UI。
  • 取消:当再次对同一 view 调用 sd_setImage(with: newURL) 时,会取消该 view 上此前由 SDWebImage 发起的任务,completed 仍可能被调用一次(cancel 语义),可通过 SDWebImageOption 或检查 finished 区分。

3.3 缓存键与 Transformer 的关系

变换后的图片会以「新 key」写入缓存:通常为 key + transformerIdentifier 或等价组合。因此:

  • 原图:key = url.absoluteString(或自定义)。
  • 变换图:key = f(url, transformer),例如 url.absoluteString + "_" + transformer.identifier

这样同一 URL 可同时存在「原图」与「缩放版」「圆角版」等多份缓存,互不覆盖;原图也可通过 SDWebImageContextOriginalImageCache 写入单独缓存实例,供大图页等使用。


七、设计模式与编程思想

1. 设计模式应用

SDWebImage 在架构上大量运用经典设计模式,使扩展与维护成本可控。

模式在 SDWebImage 中的体现作用
外观 / 门面(Facade)SDWebImageManager 对外提供 loadImage(with:options:progress:completed:),内部协调 Loader、Cache、Coder、Transformer,调用方无需关心多级缓存与管线顺序简化使用、隐藏复杂度
策略(Strategy)SDImageTransformerSDImageCoder 均为协议,多种实现可替换(Resizing、RoundCorner、WebP Coder 等),通过 context 或注册表注入算法/行为可插拔,易扩展新格式与新变换
责任链 / 管道(Chain of Responsibility / Pipeline)SDImagePipelineTransformer 将多个 Transformer 串联;解码管线中 Coder 的选取也可视为「按责任链匹配 canDecodeFromData」多步处理顺序清晰,便于组合与复用
单例 + 共享依赖(Singleton)SDWebImageManager.sharedSDImageCache.sharedSDWebImageDownloader.shared 提供默认实例,同时支持传入自定义 Cache/Loader 以打破单例全局统一入口,又保留可测试性与多实例能力
观察者 / 回调(Observer / Callback)通过 progresscompleted 闭包通知进度与结果;部分能力通过 delegate 扩展异步结果与 UI 解耦
工厂思想(Factory)SDImageCodersManager 根据 Data 格式选择 Coder;Loader 根据 URL 或 scheme 选择具体 Loader 实现创建逻辑集中,便于支持新协议与新格式

类图关系(概念层)

classDiagram
    class SDWebImageManager {
        -cache: SDImageCache
        -loader: SDImageLoader
        +loadImage(with:options:progress:completed:)
    }
    class SDImageCache {
        +store(_:forKey:)
        +queryImage(forKey:options:callback:)
    }
    class SDImageLoader {
        +loadImage(with:options:progress:completed:)
    }
    class SDImageCoder {
        <<protocol>>
        +decodedImageWithData:options:
        +canDecodeFromData:
    }
    class SDImageTransformer {
        <<protocol>>
        +transform(image:key:)
    }
    SDWebImageManager --> SDImageCache : 使用
    SDWebImageManager --> SDImageLoader : 使用
    SDWebImageManager ..> SDImageCoder : 解码时选用
    SDWebImageManager ..> SDImageTransformer : 变换时选用

2. 编程思想精华

SDWebImage 的编程思想可提炼为以下几点,对理解与模仿其设计很有帮助。

2.1 协议导向与「可替换实现」

  • CoderTransformerLoaderCache 均以协议或抽象接口呈现,具体实现可替换、可组合。
  • 新增一种图片格式或一种变换,只需实现对应协议并注册,无需改动 Manager 核心流程。这体现了开闭原则:对扩展开放,对修改关闭。

2.2 管线化与单一职责

  • 把「从 URL 到屏幕」拆成:加载 → 解码 → 变换 → 缓存 → 展示,每一步只做一件事。
  • 解码只关心 Data → Image,变换只关心 Image → Image,缓存只关心存储与查找。单一职责使每块可独立测试、优化和扩展;管线化则使数据流清晰,便于加日志、监控和插桩。

2.3 缓存键与「同一资源多形态」

  • 通过 key = f(URL, context) 的设计,同一 URL 可以对应「原图」「缩略图」「圆角图」等多条缓存,避免重复下载,又满足不同场景对尺寸/形态的需求。这体现了用键设计表达业务差异的思想。

2.4 后台处理与主线程回调

  • 解码、变换、磁盘 I/O 均在后台队列执行,completed 回调派发到主线程,兼顾性能与 UI 安全。这是移动端异步加载库的通用范式:重活放后台,结果回主线程

2.5 取消与生命周期绑定

  • View 扩展(如 UIImageView+WebCache)会把「当前正在进行的任务」与 view 绑定,当对同一 view 发起新请求时自动取消旧请求,避免错位和浪费。这体现了生命周期与请求绑定的思想,在列表场景中尤为重要。

2.6 配置通过 Context 透传

  • 不通过全局单例属性堆砌配置,而是通过 SDWebImageContext 在单次请求中传入 Cache、Transformer、Loader、CacheKeyFilter 等,使「同一 App 内不同页面/模块」可使用不同策略,且易于单元测试时注入 mock。

SDWebImage 编程思想精华一览

思想在框架中的体现
协议导向、可替换Coder / Transformer / Loader 协议化,新格式、新变换仅需实现协议并注册
管线化、单一职责加载 → 解码 → 变换 → 缓存 → 展示,每步职责单一,便于扩展与测试
键设计表达多形态同一 URL 通过 key = f(URL, context) 支持原图、缩略图、圆角图等多条缓存
后台处理、主线程回调重 CPU/IO 在后台队列,completed 回主线程,兼顾性能与 UI 安全
生命周期绑定取消View 与当前任务绑定,新请求自动取消旧请求,避免列表错位
Context 透传配置单次请求级配置,避免全局状态,利于多策略并存与测试注入

八、使用示例与最佳实践

1. 使用内置变换器(缩放 + 圆角)

let transformer = SDImagePipelineTransformer(transformers: [
    SDImageResizingTransformer(size: CGSize(width: 300, height: 300), scaleMode: .fill),
    SDImageRoundCornerTransformer(radius: 20, corners: .allCorners, borderWidth: 0, borderColor: nil)
])
imageView.sd_setImage(with: url, placeholderImage: nil, context: [.imageTransformer: transformer])

2. 仅下载并变换、不写缓存

SDWebImageManager.shared.loadImage(
    with: url,
    options: [.fromLoaderOnly],
    context: [.storeCacheType: SDImageCacheType.none.rawValue, .imageTransformer: transformer],
    progress: nil
) { image, _, _, _, _, _ in
    // 使用变换后的 image
}

3. 渐进式加载(渐进解码)

imageView.sd_setImage(with: url, placeholderImage: nil, options: [.progressiveLoad])

4. 自定义 Coder 注册(以 WebP 为例)

SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared)

5. 最佳实践小结

  • 列表/网格:cell 内先 sd_cancelCurrentImageLoad(),再 sd_setImage,并配合 Transformer 限制尺寸或使用下采样选项,减少内存与错位。
  • 预取:用 SDWebImagePrefetcher 或系统 prefetch API 提前加载即将出现的图片,滚动时优先命中缓存。
  • 大图/详情:列表用缩小版 Transformer,详情页用原图或单独 OriginalImageCache,避免重复下载。
  • 动图:使用 SDAnimatedImageView + SDAnimatedImage,并视情况注册 GIF/WebP/APNG 等 Coder。
  • 扩展与测试:自定义 Coder/Transformer 通过协议实现并注册;通过 context 注入自定义 Cache/Loader 便于单测与多策略并存。

九、常见面试题

1. 图片文件缓存的时间有多长?

一周。_maxCacheAge = kDefaultCacheMaxCacheAge

2. SDWebImage 的内存缓存是用什么实现的?

NSCache

3. SDWebImage 的最大并发数是多少?

maxConcurrentDownloads = 6(程序固定,可通过属性调整)

4. SDWebImage 支持动图吗?GIF

支持。示例:

#import <ImageIO/ImageIO.h>
[UIImage animatedImageWithImages:images duration:duration];

5. SDWebImage 是如何区分不同格式的图像的?

  • 根据图像数据第一个字节来判断
  • PNG:压缩比没有 JPG 高,但无损压缩,解压缩性能高,苹果推荐的图像格式
  • JPG:压缩比最高的一种图片格式,有损压缩,最多使用的场景如照相机,解压缩性能不好
  • GIF:序列帧动图,只支持 256 种颜色,曾流行于 1998~1999,有专利

6. SDWebImage 缓存图片的名称是怎么确定的?

  • 使用 md5 对完整 URL 做散列,得到 32 位字符串作为文件名;若单纯用文件名保存,重名几率高

7. SDWebImage 的内存警告是如何处理的?

  • 利用通知中心观察:
    • UIApplicationDidReceiveMemoryWarningNotification:接收到内存警告后执行 clearMemory,清理内存缓存
    • UIApplicationWillTerminateNotification:接收到应用将要终止后执行 cleanDisk,清理磁盘缓存
    • UIApplicationDidEnterBackgroundNotification:接收到应用进入后台后执行 backgroundCleanDisk,后台清理磁盘
  • 通过以上通知监听,保证缓存文件大小在控制范围内;clearDisk 可清空磁盘缓存,删除缓存目录中全部文件

参考文献

[1] Apple. Image and Graphics Best Practices. WWDC 2018, Session 219.
[2] Stack Overflow / Apple. Creating a thumbnail from UIImage using CGImageSourceCreateThumbnailAtIndex.
[3] Apple. Image decompression strategies for performance. developer.apple.com/forums/thread/653738.
[4] Ctrl.blog. Progressive JPEG loading; Google 研究:渐进解码约 3 倍于 baseline 的 CPU 开销.
[5] Wikipedia. Digital image processing.
[6] SDWebImage. Advanced Usage - Image Transformer, Custom Coder. GitHub Wiki.
[7] Beyer et al. FlexiViT: One Model for All Patch Sizes. CVPR 2023.
[8] NanoFLUX. Distillation-Driven Compression of Large Text-to-Image Generation Models for Mobile Devices. arXiv.
[9] SnapGen. Taming High-Resolution Text-to-Image Models for Mobile Devices. arXiv 2024.