基本框架
SDWebImage作为一个著名的iOS图像加载库,其源码主要包括以下几个核心部分:
- 图片下载管理:SDWebImageManager 是整个库的核心管理类,负责协调图片下载、缓存和处理。
- 缓存模块:SDImageCache 提供了内存缓存和磁盘缓存功能。
- 图片解码与处理:图片下载后,SDWebImage会对图片采取渐进式解码的方式加载图片。
- UIImageView分类:UIImageView (WebCache) 提供了一系列便捷的接口,如sd_setImageWithURL
- 多线程与异步处理:SDWebImage内部大量运用了GCD(Grand Central Dispatch)来处理并发任务,确保图片下载、缓存读写以及图像处理都是在后台线程执行,避免阻塞主线程影响UI流畅度。
通过网上的资料我简单的了解到该框架主要源码可以分为两部分:UIKit层和工具层
UIKit层的** UIImageView+WebCache和UIView+WebCache**为外部提供了下载图片的接口 ;
工具层的SDWImageManager是该库的核心类;它通过协调SDWebImageCache(负责缓存方面的工作),SDWebImageDownLoader(负责下载方面的工作) 实现任务的管理 ;
UIKit层(负责接收下载参数)和工具层(负责下载操作和缓存)
UIKit层
这部分源码主要包括UIImageView+WebCache和UIView+WebCache,由于UIImageView+WebCache中的接口方法的实现依赖于UIView+WebCache提供的接口 ,我们先从UIimageView+WebCahce来了解一下接口的功能和实现 ;
UIImageView+WebCache:
@interface UIImageView (WebCache)
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
- (void)sd_setImageWithURL:(nullable NSURL *)url
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* 通过指定的URL设置imageView的`image`属性,同时支持占位图、自定义下载选项以及上下文参数。
*
* 图片下载过程为异步,并且会进行缓存。
*
* @param url 图片的URL地址。
* @param placeholder 图片请求完成前要设置的初始占位图片。
* @param options 下载图片时使用的选项。有关所有可能的选项值,请参见`SDWebImageOptions`。
* @param context 一个包含不同选项以执行特定改变或过程的上下文对象,参考`SDWebImageContextOption`。此上下文用于持有`options`枚举无法容纳的额外对象。
* @param progressBlock 图片正在下载时调用的进度回调块。
* @note 进度块在后台队列中执行。
* @param completedBlock 操作完成时调用的回调块。此块没有返回值,其第一个参数为请求的UIImage对象;若发生错误,该参数为nil,第二个参数可能包含NSError对象;第三个参数是一个布尔值,表示图片是从本地缓存还是网络获取的;第四个参数是原始的图片URL。
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
通过观察ImageView+Cache.m中各个方法的实现,发现前面所有的接口都调用了
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
这个方法的实现又依赖于UIView+Cache中的
- (nullable id)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock
在这个方法中总体做了下面这几件事:
1.根据key取消当前的操作
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
从SDOperationsDictionary中根据key获取到operation执行cancle,并从SDOperationsDictionary中remove这个key对应的operation。
2.将url作为属性绑定到UIView上
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
3.根据URL,通过SDWebImageManager的loadImageWithURL方法加载图片
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
...
}
4.将上一步得到的operation,存入SDOperationsDictionary中
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
LoadImageWithURL
在理解这个方法之前,首先我们要先简单了解一下这个方法所属的对象SDWebImageManager。首先这是一个单例,在初始化的时候,还初始化了SDImageCache和SDWebImageDownloader;其次,这个单例的作用是调度SDImageCache和SDWebImageDownloader进行缓存和下载操作的 ;
在LoadImageWithURL中有一个很重要的方法:queryCacheOperationForKey()
工具层
queryCacheOperationForKey()
在解释这个方法之前,要先了解NSCache类(他的基本操作类似于NSMutableDictionary,但是他的操作是线程安全的,不需要使用者去考虑加锁和释放锁),NSCache是系统提供的一个做缓存的类 ;
SDImageCacheConfig类是一个配置类,保存一些缓存策略的信息:
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
@implementation SDImageCacheConfig
- (instancetype)init {
if (self = [super init]) {
_shouldDecompressImages = YES;
_shouldDisableiCloud = YES;
_shouldCacheImagesInMemory = YES;
_shouldUseWeakMemoryCache = YES;
_diskCacheReadingOptions = 0;
_diskCacheWritingOptions = NSDataWritingAtomic;
_maxCacheAge = kDefaultCacheMaxCacheAge;
_maxCacheSize = 0;
_diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate;
}
return self;
}
-
(void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock; -
(nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;
这两个方法是存储和查询的方法定义;
在NaCache类内部定义了一个SDMemoryCache类继承自NSCache,做后续的内存缓存操作
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType>
上面讲到了在SDWebImageManager初始化时,SDImageCache也会进行初始化;
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) { // 基类初始化
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns]; // 构建完整的命名空间字符串
// 创建IO串行队列,用于同步磁盘I/O操作
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init]; // 初始化配置对象
// 初始化内存缓存
_memCache = [[SDMemoryCache alloc] initWithConfig:_config];
_memCache.name = fullNamespace; // 设置内存缓存的名称为完整命名空间字符串
// 初始化磁盘缓存
if (directory != nil) { // 如果传入了自定义磁盘缓存目录
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace]; // 使用传入的目录路径创建磁盘缓存路径
} else { // 若未传入自定义目录,则使用默认生成路径
NSString *path = [self makeDiskCachePath:ns]; // 调用类方法生成默认路径
_diskCachePath = path;
}
dispatch_sync(_ioQueue, ^{ // 在IO串行队列上同步执行以下代码块,确保线程安全
self.fileManager = [NSFileManager new]; // 初始化文件管理器对象
});
#if SD_UIKIT // 只在UIKit环境下执行以下代码
// 订阅应用程序事件
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil]; // 注册应用即将终止通知,触发删除旧文件操作
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil]; // 注册应用进入后台通知,触发后台删除旧文件操作
#endif
}
return self; // 返回初始化后的实例
}
上面是缓存的步骤,之后就是存储和查找:
// 定义一个查询缓存的方法,接收键(key)、查询选项(options)和完成回调(doneBlock)作为参数
// 返回值类型为可空的NSOperation对象
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options
done:(nullable SDCacheQueryCompletedBlock)doneBlock {
// 如果键为空,则直接调用完成回调,返回空图像、数据和缓存类型None,并返回nil操作
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 首先检查内存缓存...
UIImage *image = [self imageFromMemoryCacheForKey:key];
// 判断是否仅查询内存缓存且已找到图像
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
// 如果仅查询内存缓存条件满足,则调用完成回调返回内存中的图像及缓存类型Memory,并返回nil操作
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// 创建一个新的NSOperation对象
NSOperation *operation = [NSOperation new];
// 定义一个查询磁盘缓存的闭包
void(^queryDiskBlock)(void) = ^{
// 如果操作已被取消,则不调用完成回调并直接返回
if (operation.isCancelled) {
return;
}
// 使用自动释放池来优化内存管理
@autoreleasepool {
// 查询磁盘缓存获取数据
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
// 初始化变量:磁盘图像、缓存类型
UIImage *diskImage = nil;
SDImageCacheType cacheType = SDImageCacheTypeNone;
// 如果内存中有图像,则使用内存中的图像,设置缓存类型为Memory
if (image) {
diskImage = image;
cacheType = SDImageCacheTypeMemory;
}
// 否则,如果磁盘有数据,则解码数据得到图像,设置缓存类型为Disk
else if (diskData) {
cacheType = SDImageCacheTypeDisk;
diskImage = [self diskImageForKey:key data:diskData options:options];
// 如果配置要求缓存图像到内存且成功解码了磁盘图像
if (diskImage && self.config.shouldCacheImagesInMemory) {
// 计算图像的内存成本,并将其加入内存缓存
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
// 如果存在完成回调
if (doneBlock) {
// 根据查询选项判断是否同步回调结果
if (options & SDImageCacheQueryDiskSync) {
// 同步回调磁盘图像、数据及缓存类型
doneBlock(diskImage, diskData, cacheType);
} else {
// 异步在主线程回调磁盘图像、数据及缓存类型
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
// 根据查询选项决定是否同步执行查询磁盘闭包
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
// 异步在I/O队列上执行查询磁盘闭包
dispatch_async(self.ioQueue, queryDiskBlock);
}
// 返回创建的NSOperation对象
return operation;
}
这段代码实现了一个查询缓存的方法,它首先检查给定键对应的图像是否存在于内存缓存中。如果仅需要查询内存缓存且内存缓存已有图像,则直接调用完成回调并返回。否则,创建一个NSOperation对象,并定义一个查询磁盘缓存的闭包。该闭包在适当的条件下(同步或异步)查询磁盘缓存,获取图像数据并可能解码为UIImage对象。如果配置允许,将解码后的磁盘图像加入内存缓存。最后,根据查询选项选择同步或异步在主线程上回调查询结果。整个方法返回创建的NSOperation对象,以便外部可以取消查询操作或将其添加到操作队列中。
到这里其实可以整理一下,缓存的流水线设计了:
1.先查内存
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}
2.查询磁盘缓存,这里可能会造成内存峰值,所以使用了@autoreleasepool,并在ioQueue中异步执行
// 定义一个方法,接收键(key)作为参数,返回键所对应的磁盘缓存数据
// 方法在搜索所有可能路径时(包括默认路径和自定义路径),会考虑键可能带有扩展名或没有扩展名的情况
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
// 1. 获取默认缓存路径
NSString *defaultPath = [self defaultCachePathForKey:key];
// 2. 尝试从默认路径加载数据(带有扩展名)
NSData *data = [NSData dataWithContentsOfFile:defaultPath
options:self.config.diskCacheReadingOptions
error:nil];
if (data) {
// 如果找到数据,直接返回
return data;
}
// 3. 因为历史版本(参考SDWebImage#976)曾将原始扩展名添加到哈希文件名中,所以这里作为回退,尝试从默认路径加载不带扩展名的数据
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension
options:self.config.diskCacheReadingOptions
error:nil];
if (data) {
// 如果找到数据,直接返回
return data;
}
// 4. 获取所有自定义缓存路径列表的副本
NSArray<NSString *> *customPaths = [self.customPaths copy];
// 5. 遍历自定义路径列表
for (NSString *path in customPaths) {
// 6. 为给定键计算当前自定义路径下的缓存文件路径
NSString *filePath = [self cachePathForKey:key inPath:path];
// 7. 尝试从自定义路径加载数据(带有扩展名)
NSData *imageData = [NSData dataWithContentsOfFile:filePath
options:self.config.diskCacheReadingOptions
error:nil];
if (imageData) {
// 如果找到数据,直接返回
return imageData;
}
// 8. 同样考虑回退情况,尝试从自定义路径加载不带扩展名的数据
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension
options:self.config.diskCacheReadingOptions
error:nil];
if (imageData) {
// 如果找到数据,直接返回
return imageData;
}
}
// 9. 若在所有路径中均未找到数据,返回nil
return nil;
}
3.磁盘查找成功就根据缓存策略判断是否要写入缓存
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
4.最后执行完成的doneBlock回调
if (doneBlock) {
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
downloadImageWithURL
这部部分方法负责图片的下载:
downloadImageWithURL方法返回的是一个SDWebImageDownloadToken类型的token,
可以在取消的回调中及时取消下载操作 ;
@implementation SDWebImageCombinedOperation
// 实现取消操作的方法
- (void)cancel {
// 使用@synchronized关键字确保取消操作的线程安全性
@synchronized(self) {
// 标记当前操作为已取消
self.cancelled = YES;
// 如果存在缓存操作(cacheOperation),则取消该操作并将其置为nil
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
// 如果存在下载令牌(downloadToken),则通过manager的imageDownloader取消对应的下载任务
if (self.downloadToken) {
[self.manager.imageDownloader cancel:self.downloadToken];
}
// 通知manager从正在运行的操作列表中安全移除当前操作
[self.manager safelyRemoveOperationFromRunning:self];
}
}
@end
这段代码实现了 SDWebImageCombinedOperation 类的 -cancel 方法。该方法用于取消当前的组合操作,它包括以下步骤:
1.使用 @synchronized(self) 语句确保整个取消过程在多线程环境下是线程安全的。
2.将实例变量 cancelled 标记为 YES,表示当前操作已被取消。
3.如果存在关联的缓存操作(cacheOperation),调用其 -cancel 方法取消缓存操作,并将 cacheOperation 属性置为 nil,表明已取消并释放了关联的缓存操作。
4.如果存在下载令牌(downloadToken),通过 self.manager.imageDownloader 对象调用 -cancel: 方法,传入 self.downloadToken 参数,取消正在进行的下载任务。
5.最后,通知 self.manager 调用 -safelyRemoveOperationFromRunning: 方法,将当前的 SDWebImageCombinedOperation 实例从正在运行的操作列表中移除,表明此操作已不再处于活动状态。
// 检查给定的operation是否为nil、已结束或已取消
if (!operation || operation.isFinished || operation.isCancelled) {
// 如果满足条件,创建一个新的下载器操作(downloader operation)
operation = [self createDownloaderOperationWithUrl:url options:options];
// 创建弱引用指向self的临时变量wself,用于在completionBlock内部捕获self
__weak typeof(self) wself = self;
// 为新创建的操作设置完成块(completionBlock)
operation.completionBlock = ^{
// 在block内部使用强引用sself恢复对self的引用
__strong typeof(wself) sself = wself;
// 如果sself为nil(意味着self已经被释放),则直接返回,避免对已释放的对象进行操作
if (!sself) {
return;
}
// 加锁,确保对URLOperations字典的访问是线程安全的
LOCK(sself.operationsLock);
// 从URLOperations字典中移除与url关联的操作
[sself.URLOperations removeObjectForKey:url];
// 解锁,释放对operationsLock的控制
UNLOCK(sself.operationsLock);
};
// 将新创建的操作以url为键添加到URLOperations字典中
[self.URLOperations setObject:operation forKey:url];
// 根据Apple文档建议,确保所有配置完成后才将操作添加到操作队列(downloadQueue)
// 注意:`addOperation:`不会立即同步执行operation.completionBlock,因此不会导致死锁
[self.downloadQueue addOperation:operation];
}
上面的代码实现的是真正的网络请求下载;createDownloaderOperationWithUrl方法中,创建了一个request,最后返回了一个operation,operation其实是一个SDWebImageDownloaderOperation的实例,这个在初始化sessionConfiguration的时候就设置了。
// 定义一个初始化方法,接收一个可选的NSURLSessionConfiguration对象作为参数
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
// 调用父类的初始化方法创建实例
if ((self = [super init])) {
// 设置操作类为SDWebImageDownloaderOperation,用于处理实际的下载任务
_operationClass = [SDWebImageDownloaderOperation class];
// 默认开启图像解压缩,下载完成后直接提供解压缩后的UIImage对象
_shouldDecompressImages = YES;
// 设置执行顺序为先进先出(FIFO)
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
// 创建一个新的NSOperationQueue用于管理下载任务
_downloadQueue = [NSOperationQueue new];
// 设置最大并发操作数为6,限制同时进行的下载任务数量
_downloadQueue.maxConcurrentOperationCount = 6;
// 给下载队列命名,便于调试和识别
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
// 初始化一个字典用于存储正在进行的URL操作
_URLOperations = [NSMutableDictionary new];
// 初始化一个SDHTTPHeadersMutableDictionary实例,用于存储自定义HTTP头信息(代码省略)
// 使用传入的sessionConfiguration创建新的NSURLSession,并进行其他初始化工作(代码省略)
[self createNewSessionWithConfiguration:sessionConfiguration];
}
// 返回初始化后的实例
return self;
}
这段代码定义了SDWebImageDownloader类的一个初始化方法,接收一个NSURLSessionConfiguration对象作为参数。
SDWebImageDownloaderOperation继承自NSOperation,并且重写了NSOperation的start()方法,这里才是网络请求真正开始的地方。
downloadImageWithURL中[self.downloadQueue addOperation:operation];这句代码就是触发网络请求的关键所在。
// 实现开始下载任务的方法
- (void)start {
// 使用@synchronized关键字确保开始下载过程的线程安全性
@synchronized (self) {
// 如果任务已被取消,则标记为已结束,重置任务状态并直接返回
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT // 只在UIKit环境下执行以下代码
// 检测是否存在UIApplication类并具有sharedApplication方法
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
// 如果当前应用支持后台下载且应继续在应用进入后台时执行
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
// 使用弱引用避免循环引用
__weak __typeof__(self) wself = self;
// 获取UIApplication单例
UIApplication *app = [UIApplicationClass performSelector:@selector(sharedApplication)];
// 开始后台任务,当任务即将过期时调用取消方法
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
[wself cancel];
}];
}
#endif
// 获取当前任务所属的 URLSession(可能是未持有引用的)
NSURLSession *session = self.unownedSession;
// 如果当前任务没有关联的 URLSession,则创建一个新的 URLSession
if (!session) {
// 使用默认配置创建一个新的 URLSession 配置对象
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15; // 设置请求超时时间为15秒
// 根据配置创建一个新的 URLSession,并设置其代理为当前对象(self),使用系统自动创建的串行队列处理代理回调
session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
// 保存新创建的 URLSession,并持有引用
self.ownedSession = session;
}
// 如果需要忽略缓存响应,则获取并保存当前请求的缓存数据
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
NSURLCache *URLCache = session.configuration.URLCache ?: [NSURLCache sharedURLCache];
@synchronized (URLCache) { // 由于 NSURLCache 的 `cachedResponseForRequest:` 不是线程安全的,需要同步访问
NSCachedURLResponse *cachedResponse = [URLCache cachedResponseForRequest:self.request];
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
}
// 创建并保存一个数据任务,使用当前请求
self.dataTask = [session dataTaskWithRequest:self.request];
// 标记任务已经开始执行
self.executing = YES;
}
// 如果数据任务已创建成功
if (self.dataTask) {
// 检查任务是否支持优先级设置,并根据选项设置优先级
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
}
#pragma clang diagnostic pop
// 开始执行数据任务
[self.dataTask resume];
// 发送进度回调,初始进度为0,长度未知
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
// 弱引用自身并在主线程发布下载开始的通知
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
});
} else {
// 如果数据任务未能创建,调用错误回调,发布完成通知并标记任务为完成
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
}
}
到这里,下载流程就结束了 ;