SDWebImage 源码

基本框架

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];
    }
}

到这里,下载流程就结束了 ;

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值