SDWebImage 源码阅读笔记(一)

本文深入解析了SDWebImage的工作原理,介绍了如何利用UIImageView+WebCache类别实现图片的异步加载与缓存管理。探讨了UIImageView+WebCache的具体实现细节,包括SDWebImageManager的运作方式及其与其他组件间的协同工作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


简介

Asynchronous image downloader with cache support as a UIImageView category.

言简意赅:SDWebImage 以 UIImageView category(分类)的形式,来支持图片的异步下载与缓存。

其提供了以下功能:

  1. 以 UIImageView 的分类,来支持网络图片的加载与缓存管理
  2. 一个异步的图片加载器
  3. 一个异步的内存 + 磁盘图片缓存
  4. 支持 GIF
  5. 支持 WebP
  6. 后台图片解压缩处理
  7. 确保同一个 URL 的图片不被多次下载
  8. 确保虚假的 URL 不会被反复加载
  9. 确保下载及缓存时,主线程不被阻塞
  10. 使用 GCD 与 ARC
  11. 支持 Arm64

UIImageView+WebCache

首先,SDWebImage 最常见的使用场景想必如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import <SDWebImage/UIImageView+WebCache.h>

...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *MyIdentifier = @"MyIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:MyIdentifier] autorelease];
    }

    // 在这里,我们使用 UIImageView 分类提供的 sd_setImageWithURL: 方法来加载网络图片
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
                      placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

    cell.textLabel.text = @"My Text";
    return cell;
}

我们在使用 UITableView 时,往往需要在 Cell 上显示来自网络的图片,这里最关键的一行代码便是:

1
2
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
                  placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

于是我们「CMD + 左键」来到了 UIImageView+WebCache 查看具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * 根据 url、placeholder 与 custom options 为 imageview 设置 image
 *
 * 下载是异步的,并且被缓存的
 *
 * @param url            网络图片的 url 地址
 * @param placeholder    用于预显示的图片
 * @param options        一些定制化选项
 * @param progressBlock  下载时的 Block,其定义为:typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
 * @param completedBlock 下载完成时的 Block,其定义为:typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }
    
    if (url) {
        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        dispatch_main_async_safe(^{
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            if (completedBlock) {
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

虽然代码只有几十行,但其中涉及到的知识点却可不少哦,不要急,让我们将迷雾一层层剥开:


UIView+WebCacheOperation

首先来看:

1
[self sd_cancelCurrentImageLoad];

「CMD + 左键」后带我们来到了 UIView+WebCacheOperation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // 取消正在进行的下载队列
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    id operations = [operationDictionary objectForKey:key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

框架中的所有操作实际上都是通过一个 operationDictionary(具体查看 UIView+WebCacheOperation)来管理的,而这个 Dictionary 实际上是通过动态的方式(详情可参见:Objective-C Associated Objects 的实现原理)添加到 UIView 上的一个属性,至于为什么添加到 UIView 上, 主要是因为这个 operationDictionary 需要在 UIButton 和 UIImageView 上重用,所以需要添加到它们的根类上。

当执行 sd_setImageWithURL: 函数时,首先会 cancel 掉 operationDictionary 中已经存在的 operation,并重新创建一个新的 SDWebImageCombinedOperation 对象来获取 image,该 operation 会被存入 operationDictionary 中。

这样来保证每个 UIImageView 对象中永远只存在一个 operation,当前只允许一个图片网络请求,该 operation 负责从缓存中获取 image 或者是重新下载 image。

SDWebImageCombinedOperation 的 cancel 操作同时会 cacel 掉缓存查询的 operation 以及 downloader 的 operation


dispatch_main_sync_safe & dispatch_main_async_safe 宏定义

再来看:

1
2
3
dispatch_main_async_safe(^{
           self.image = placeholder;
       });

上述代码中的 dispatch_main_sync_safedispatch_main_async_safe 均为宏定义, 点进去一看发现宏是这样定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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);\
    }

相信你通过这两个宏的名字就能猜到它们的作用了: 因为图像的绘制只能在主线程完成,所以dispatch_main_sync_safedispatch_main_async_safe 就是为了保证 block 能在主线程中执行。


SDWebImageManager

SDWebImageManager.h 中你可以看到关于 SDWebImageManager 的描述:

The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). You can use this class directly to benefit from web image downloading with caching in another context than a UIView.

这个类就是隐藏在 UIImageView+WebCache 背后,用于处理异步下载和图片缓存的类,当然你也可以直接使用 SDWebImageManager 的上述方法 downloadImageWithURL:options:progress:completed: 来直接下载图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * 如果在缓存中则直接返回,否则根据所给的 URL 下载图片
 * 
 * @param url            网络图片的 url 地址
 * @param options        一些定制化选项
 * @param progressBlock  下载时的 Block,其定义为:typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
 * @param completedBlock 下载完成时的 Block,其定义为:typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
 * @return 				 返回 SDWebImageOperation 的实例
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    /**
     * 前面省略 n 行,主要作了如下处理:
     * 1. 判断 url 的合法性  
     * 2. 创建 SDWebImageCombinedOperation 对象  
     * 3. 查看 url 是否是之前下载失败过的  
     * 4. 如果 url 为 nil,或者在不可重试的情况下是一个下载失败过的 url,则直接返回操作对象并调用完成回调 
    */
    // 根据 URL 生成对应的 key,没有特殊处理为 [url absoluteString];
    NSString *key = [self cacheKeyForURL:url];
    // 去缓存中查找图片(参见 SDImageCache)
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) 
    {
       /* ... */
       // 如果在缓存中没有找到图片,或者采用的 SDWebImageRefreshCached 选项,则从网络下载
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
                dispatch_main_sync_safe(^{
                  // 如果图片找到了,但是采用的 SDWebImageRefreshCached 选项,通知获取到了图片,并再次从网络下载,使 NSURLCache 重新刷新
                     completedBlock(image, nil, cacheType, YES, url);
                });
            }
            /* 下载选项设置 */ 
            // 使用 imageDownloader 开启网络下载
            id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
                /* ... */
               if (downloadedImage && finished) {
                     // 下载完成后,先将图片保存到缓存中,然后主线程返回
                     [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }
                     dispatch_main_sync_safe(^{
                            if (!weakOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }
          /* ... */
       }
        else if (image) {
          // 在缓存中找到图片了,直接返回
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
        }
    }];
    return operation;
}

更详细的注解可参见:SDWebImage源码解析之SDWebImageManager的注解

要点

  1. 在 SDWebImageManager 中管理了一个 failedURLs 的 NSMutableSet,里面下载失败的 url 会被存储下来。同时,可以通过 SDWebImageRetryFailed 来强制继续重试下载

  2. 查找缓存,若缓存中没有 image 则通过 SDWebImageDownloader 来进行下载,下载完成后通过 SDImageCache 进行缓存,会同时缓存到 memCache 和 diskCache 中


可以看到 SDWebImageManager 这个类的主要作用就是为 UIImageView+WebCache 和 SDWebImageDownloader,SDImageCache 之间构建一个桥梁,使它们能够更好的协同工作,在接下来的系列文章中,就让我们一探究竟:它是如何协调异步下载和图片缓存的?


原文:http://itangqi.me/2016/03/19/the-notes-of-learning-sdwebimage-one/


内容概要:本文档详细介绍了个基于MATLAB实现的跨尺度注意力机制(CSA)结合Transformer编码器的多变量时间序列预测项目。项目旨在精准捕捉多尺度时间序列特征,提升多变量时间序列的预测性能,降低模型计算复杂度与训练时间,增强模型的解释性和可视化能力。通过跨尺度注意力机制,模型可以同时捕获局部细节和全局趋势,显著提升预测精度和泛化能力。文档还探讨了项目面临的挑战,如多尺度特征融合、多变量复杂依赖关系、计算资源瓶颈等问题,并提出了相应的解决方案。此外,项目模型架构包括跨尺度注意力机制模块、Transformer编码器层和输出预测层,文档最后提供了部分MATLAB代码示例。 适合人群:具备定编程基础,尤其是熟悉MATLAB和深度学习的科研人员、工程师和研究生。 使用场景及目标:①需要处理多变量、多尺度时间序列数据的研究和应用场景,如金融市场分析、气象预测、工业设备监控、交通流量预测等;②希望深入了解跨尺度注意力机制和Transformer编码器在时间序列预测中的应用;③希望通过MATLAB实现高效的多变量时间序列预测模型,提升预测精度和模型解释性。 其他说明:此项目不仅提供了种新的技术路径来处理复杂的时间序列数据,还推动了多领域多变量时间序列应用的创新。文档中的代码示例和详细的模型描述有助于读者快速理解和复现该项目,促进学术和技术交流。建议读者在实践中结合自己的数据集进行调试和优化,以达到最佳的预测效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值