深入理解SDWebImage(三)

按照SDWebImage中给到的主流程,进入到SDWebImageManager,SDWebImageManager总结,按照资源的查找顺序我们梳理一下SDImageCache和SDWebImageDownloader。

SDImageCache

在上一篇文章中图片从缓存中进行query,以及图片下载完只有的store,都是进入了SDImageCache中,并且SDImageCache是一个单例对象,我们总结一下类中的功能点:

(1)、内部生成了分别处理内存缓存/磁盘缓存的两个属性memoryCache/diskCache,在memoryCache内部用于保存和读取内存中的图片,在diskCache中可以根据key来设置和读取磁盘数据,删除过期数据等

(2)、设置defaultCacheConfig,来设置磁盘缓存大小(没有设置),磁盘缓存时间(7天),是否允许内存缓存、是否进入后台自动清除过期磁盘数据等

(3)、接收UIApplicationWillTerminateNotification通知,程序被杀死时,会调用deleteOldFilesWithCompletionBlock,通过diskCache来清理磁盘缓存。

  • 先获取当前磁盘目录,遍历当前磁盘目录URL,查找磁盘数据中所有修改日期超过最大磁盘缓存时间的数据,清除超时数据

  • 在遍历磁盘数据的时候,更新未超时的本地磁盘占用大小;如果最后超过了规定的磁盘占用大小,对磁盘文件的修改时间进行排序,然后for循环中移除最老的数据,直到占用磁盘总大小小于设置的磁盘最大占用

(4)、UIApplicationDidEnterBackgroundNotification通知,进入后台还是调用deleteOldFilesWithCompletionBlock,删除数据缓存

(5)、在SDImageCache方法中,定义了存储、查询、删除图片的方法。

首先我们看一下初始化SDImageCache方法,是一个单例,并且初始化各种属性以及添加对app的监听

//单例方法
+ (nonnull instancetype)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
//初始化主要用到了下面的方法
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nullable NSString *)directory
                                   config:(nullable SDImageCacheConfig *)config {
    if ((self = [super init])) {
        NSAssert(ns, @"Cache namespace should not be nil");
        
        // Create IO serial queue
        //在串行队列异步线程中进行数据的IO
        _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL);
        
        if (!config) {
            config = SDImageCacheConfig.defaultCacheConfig;
        }
        _config = [config copy];
        
        // Init the memory cache
        //初始化内存缓存
        NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)], @"Custom memory cache class must conform to `SDMemoryCache` protocol");
        _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
        
        // Init the disk cache
        //初始化磁盘缓存地址
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:ns];
        } else {
            NSString *path = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:ns];
            _diskCachePath = path;
        }
        
        NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
        //初始化磁盘缓存工具类
        _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
        
        // Check and migrate disk cache directory if need
        [self migrateDiskCacheDirectory];

#if SD_UIKIT
        // Subscribe to app events
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidEnterBackground:)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
#if SD_MAC
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:NSApplicationWillTerminateNotification
                                                   object:nil];
#endif
    }

    return self;
}

图片初始化完之后需要保存图片到磁盘或者内存中

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
          toMemory:(BOOL)toMemory
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //将image对象加入可以放入内存中
    if (toMemory && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = image.sd_memoryCost;
        //将图片image放入NSCache中的NSMapTable中,并记录当前总共占用多少内存
        [self.memoryCache setObject:image forKey:key cost:cost];
    }
    
    if (toDisk) {
        //假如放入磁盘,这个过程中会有比较大的内存消耗,需要放入自动释放池
        //放入磁盘中的是二进制data,并且包含指定的图片格式信息
        //放入磁盘中
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
                    SDImageFormat format;
                    //如果包含alpha,那么PNG,否则JPEG
                    if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
                        format = SDImageFormatPNG;
                    } else {
                        format = SDImageFormatJPEG;
                    }
                    //假如没有data,有image,那么按照指定的格式对图片进行压缩
                    data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
                }
                //放入磁盘中
                [self _storeImageDataToDisk:data forKey:key];
            }
            
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}

//磁盘中
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    
    [self.diskCache setData:imageData forKey:key];
}

内存或者磁盘分别调用了SDMemoryCache/SDDiskCache来对内存和缓存图片进行处理,并由SDImageCacheConfig来设置缓存的策略,包括是否需要内存缓存、最大磁盘容量、最大磁盘缓存时长等

SDImageCacheConfig

static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week


/**
 *不用icloud中的图片,默认是YES.
 */
@property (assign, nonatomic) BOOL shouldDisableiCloud;

/**
 //在缓存中缓存图片,默认是YES
 */
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

/*
 //设置该选项表示将图片放入内存中的同时也会保存一份到maptable中(弱引用)
 //当内存清理之后,由于某些images还被UIImageViews强引用,所以image并不会释放
 //由于maptable的弱引用存在,内存中的某些image就可以恢复
 //这样可以保证某些图片不会进入磁盘查找/重新下载
 */
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;

/**
 //进入后台之后清理过期的磁盘中的图片,默认是YES
 */
@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground;

/**
 //从磁盘读取文件到内存中的option
 //默认是0
 */
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;

/**
 //写入disk的option
 //默认是NSDataWritingAtomic,也可以设置成NSDataWritingWithoutOverwriting,避免之前的数据被覆盖
 */
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;

/**
 //磁盘中的超时时间,默认一周
 */
@property (assign, nonatomic) NSTimeInterval maxDiskAge;

/**
 //最大的磁盘容量,默认没有限制
 */
@property (assign, nonatomic) NSUInteger maxDiskSize;
/**
 //最大内存容量,默认没有限制
 */
@property (assign, nonatomic) NSUInteger maxMemoryCost;

/**
 //最大内存对象个数
 */
@property (assign, nonatomic) NSUInteger maxMemoryCount;

/*
 //磁盘缓存的过期类型
 */
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;

/**
 */
@property (strong, nonatomic, nullable) NSFileManager *fileManager;

/**
 //处理缓存的类SDMemoryCache
 */
@property (assign, nonatomic, nonnull) Class memoryCacheClass;

/**
 //SDDiskCache类
 */
@property (assign ,nonatomic, nonnull) Class diskCacheClass;

@end

SDMemoryCache

SDMemoryCache继承自NSCache,内部遵守SDMemoryCache协议,用来设置缓存,获取缓存数据。

(1)、对内存数据进行增(set)、删(remove)、查(objectForKey),方法内存实现父类的方法

(2)、在实现上边过程中,需要往本地NSMapTable中也进行删除过程,并进行弱引用,当内存中没有查询到数据,但是从MapTable中查询到数据,需要将数据放到父类NSCache中一份,实现了内存恢复的作用

(3)、监听config中maxMemoryCost改变,同步到NSCache中

- (void)commonInit {
    //设置默认的config
    SDImageCacheConfig *config = self.config;
    self.totalCostLimit = config.maxMemoryCost;
    self.countLimit = config.maxMemoryCount;
    
    //对config中的maxMemoryCost、maxMemoryCount添加监听
    [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
    [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
    
#if SD_UIKIT
    //声明NSPointerFunctionsWeakMemory的NSMapTable
    self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
    self.weakCacheLock = dispatch_semaphore_create(1);
    
    //监听系统的内存警告
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveMemoryWarning:)
                                                 name:UIApplicationDidReceiveMemoryWarningNotification
                                               object:nil];
#endif
}

#if SD_UIKIT
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
    // 内存警告
    [super removeAllObjects];
}

//依据setObject:forKey: 放入NSCache中
//并记录内存中增加了多少
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    //分裂的NSCache
    [super setObject:obj forKey:key cost:g];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key && obj) {
        //信号量,加锁来保证内存访问安全
        SD_LOCK(self.weakCacheLock);
        //本地保存mapTable
        [self.weakCache setObject:obj forKey:key];
        SD_UNLOCK(self.weakCacheLock);
    }
}

//查询数据,如果内存没查到,但本地MapTable中查找到了,放入内存一份
- (id)objectForKey:(id)key {
    //分类中查找
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        //当清除掉缓存中的数据,但是mapTable中有弱引用
        //同步到当前NSCache中
        SD_LOCK(self.weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        SD_UNLOCK(self.weakCacheLock);
        if (obj) {
            //可以看到拿到的就是UIImage
            NSUInteger cost = 0;
            if ([obj isKindOfClass:[UIImage class]]) {
                cost = [(UIImage *)obj sd_memoryCost];
            }
            [super setObject:obj forKey:key cost:cost];
        }
    }
    return obj;
}

//删除
- (void)removeObjectForKey:(id)key {
    [super removeObjectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key) {
        //mapTable删除
        SD_LOCK(self.weakCacheLock);
        [self.weakCache removeObjectForKey:key];
        SD_UNLOCK(self.weakCacheLock);
    }
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if (context == SDMemoryCacheContext) {
        //监听内存的设置,同步到NSCache中totalCostLimit、countLimit
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) {
            self.totalCostLimit = self.config.maxMemoryCost;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) {
            self.countLimit = self.config.maxMemoryCount;
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

SDImageCodersManager

图片的磁盘缓存过程中用到了对图片进行类型判断、encode、decode的过程,我们先看一下这一块的代码。

(1)、encode/decode过程用到了SDImageCodersManager,内部将所有的遵循SDImageCoder协议的类都放入Manager数组中,每次进行decode或者encode处理,都会进行遍历,对应到指定的coder类中进行处理。

(2)、SDImageCoder协议中声明了是否可以进行encode/decode,以及将image传递进来进行encode/decode的操作;包含SDImageIOCoder - 支持PNG、JPEG、TIFF;SDImageGIFCoder - 支持对GIF类型进行编解码;SDImageAPNGCoder - APNG格式的图片进行编解码。

(3)、NSData的图片格式判断是在NSData+ImageContentType分类中进行的

{
    NSMutableArray<id<SDImageCoder>> *_imageCoders;
}
//单例进行处理
+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

//初始化本地可以进行code的类SDImageGIFCoder、SDImageAPNGCoder等都是单例
- (instancetype)init {
    if (self = [super init]) {
        // initialize with default coders
        _imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
        _codersLock = dispatch_semaphore_create(1);
    }
    return self;
}

//进行压缩处理
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    NSArray<id<SDImageCoder>> *coders = self.coders;
   //根据对应的格式,交给指定的encode类去进行压缩处理
    for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
        if ([coder canEncodeToFormat:format]) {
            return [coder encodedDataWithImage:image format:format options:options];
        }
    }
    return nil;
}

拿GIF图的decode来了解具体的压缩解压缩编码 - SDImageGIFCoder
通过CGImageSourceRef绘制拿到UIImage,将UIImage转化成每一张图片的CGImageRef并进行拼合

- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
    if (!data) {
        return nil;
    }
    CGFloat scale = 1;
    //外部设置的图片缩放范围
    NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
    if (scaleFactor != nil) {
        scale = MAX([scaleFactor doubleValue], 1);
    }
    
    //图片上下文资源
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    if (!source) {
        return nil;
    }
    size_t count = CGImageSourceGetCount(source);
    UIImage *animatedImage;
    
    //是否只获取第一帧
    BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
    if (decodeFirstFrame || count <= 1) {
        //假如是,那么直接生成图片
        animatedImage = [[UIImage alloc] initWithData:data scale:scale];
    } else {
        NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
        
        //根据获得的资源中的count来遍历图片ref
        for (size_t i = 0; i < count; i++) {
            CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
            if (!imageRef) {
                continue;
            }
            
            float duration = [self sd_frameDurationAtIndex:i source:source];
            UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
            CGImageRelease(imageRef);
            
            SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
            [frames addObject:frame];
        }
        
        NSUInteger loopCount = [self sd_imageLoopCountWithSource:source];
        
        animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
        animatedImage.sd_imageLoopCount = loopCount;
    }
    animatedImage.sd_imageFormat = SDImageFormatGIF;
    CFRelease(source);
    
    return animatedImage;
}


- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    
    if (format != SDImageFormatGIF) {
        return nil;
    }
    
    NSMutableData *imageData = [NSMutableData data];
    //图片的CFStringRef 为kUTTypeGIF
    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF];
    NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
    
    // Create an image destination. GIF does not support EXIF image orientation
    // The `CGImageDestinationCreateWithData` will log a warning when count is 0, use 1 instead.
    //图片的imageUTType生成对应的CGImageDestinationRef
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count ?: 1, NULL);
    if (!imageDestination) {
        // Handle failure.
        return nil;
    }
    
    //GIF的CFDictionaryRef
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    double compressionQuality = 1;
    if (options[SDImageCoderEncodeCompressionQuality]) {
        compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
    }
    properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
    
    BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
    if (encodeFirstFrame || frames.count == 0) {
        // for static single GIF images
        //获取一个单一的图片
        CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
    } else {
        // for animated GIF images
        //获取分类中添加的sd_imageLoopCount数量
        NSUInteger loopCount = image.sd_imageLoopCount;
        //设置GIFLoopCount
        NSDictionary *gifProperties = @{(__bridge NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)};
        properties[(__bridge NSString *)kCGImagePropertyGIFDictionary] = gifProperties;
        //设置GIF图片的属性
        CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)properties);
        
        for (size_t i = 0; i < frames.count; i++) {
            SDImageFrame *frame = frames[i];
            float frameDuration = frame.duration;
            CGImageRef frameImageRef = frame.image.CGImage;
            NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
            CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
        }
    }
    // Finalize the destination.
    //结果
    if (CGImageDestinationFinalize(imageDestination) == NO) {
        // Handle failure.
        imageData = nil;
    }
    
    CFRelease(imageDestination);
    
    return [imageData copy];
}

在图片格式的获取或者转化成NSData时都用到了 - NSData+ImageContentType

//判断data数据获取image的格式
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    uint8_t c;
    //将我们的data转化为data
    //获取data中第一个字节,这里存放着图片的类型
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52: {
            if (data.length >= 12) {
                //RIFF....WEBP
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
                if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                    return SDImageFormatWebP;
                }
            }
            break;
        }
        case 0x00: {
            if (data.length >= 12) {
                //....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
                NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
                if ([testString isEqualToString:@"ftypheic"]
                    || [testString isEqualToString:@"ftypheix"]
                    || [testString isEqualToString:@"ftyphevc"]
                    || [testString isEqualToString:@"ftyphevx"]) {
                    return SDImageFormatHEIC;
                }
                //....ftypmif1 ....ftypmsf1
                if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
                    return SDImageFormatHEIF;
                }
            }
            break;
        }
    }
    return SDImageFormatUndefined;
}
//NSData中对应的图片类型
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format {
    CFStringRef UTType;
    switch (format) {
        case SDImageFormatJPEG:
            UTType = kUTTypeJPEG;
            break;
        case SDImageFormatPNG:
            UTType = kUTTypePNG;
            break;
        case SDImageFormatGIF:
            UTType = kUTTypeGIF;
            break;
        case SDImageFormatTIFF:
            UTType = kUTTypeTIFF;
            break;
        case SDImageFormatWebP:
            UTType = kSDUTTypeWebP;
            break;
        case SDImageFormatHEIC:
            UTType = kSDUTTypeHEIC;
            break;
        case SDImageFormatHEIF:
            UTType = kSDUTTypeHEIF;
            break;
        default:
            // default is kUTTypePNG
            UTType = kUTTypePNG;
            break;
    }
    return UTType;
}

SDDiskCache

SDDiskCache中定义的方法和SDMemoryCache中的方法类似,都是设置缓存,获取缓存。

(1)、对磁盘数据进行增(set)、删(remove)、查(objectForKey),如果有磁盘缓存了当前数据,需要进行数据更新,以保证后台磁盘中的修改时间得到更新

(2)、放到磁盘中其实是对key(url+其他image额外的信息)进行16位的MD5加密

(3)、删除过期的磁盘数据,首先按照时间进行排序,然后删除

增删查的处理

- (BOOL)containsDataForKey:(NSString *)key {
    NSParameterAssert(key);
    //查询文件路径
    NSString *filePath = [self cachePathForKey:key];
    BOOL exists = [self.fileManager fileExistsAtPath:filePath];

    if (!exists) {
        exists = [self.fileManager fileExistsAtPath:filePath.stringByDeletingPathExtension];
    }
    
    return exists;
}

- (NSData *)dataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    // 可能没有 path extension
    data = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    
    return nil;
}

- (void)setData:(NSData *)data forKey:(NSString *)key {
    NSParameterAssert(data);
    NSParameterAssert(key);
    if (![self.fileManager fileExistsAtPath:self.diskCachePath]) {
        //假如文件不存在,那么生成一个文件夹,
        [self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    //拼接url字符串生成key
    NSString *cachePathForKey = [self cachePathForKey:key];
    // transform to NSUrl
    //文件地址
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    
    [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
    
    //不包含iiCloud
    if (self.config.shouldDisableiCloud) {
        // ignore iCloud backup resource value error
        
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

- (void)removeDataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    [self.fileManager removeItemAtPath:filePath error:nil];
}

- (void)removeAllData {
    [self.fileManager removeItemAtPath:self.diskCachePath error:nil];
    [self.fileManager createDirectoryAtPath:self.diskCachePath
            withIntermediateDirectories:YES
                             attributes:nil
                                  error:NULL];
}

MD5进行加密

static inline NSString * _Nonnull SDDiskCacheFileNameForKey(NSString * _Nullable key) {
    //KEY中除了url信息之外,还有我们的context中的key比如 url-SDImageRoundCornerTransformer(400.000000,1,10.000000,#ff0000ff)
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    //16位的MD5值
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    //对key进行MD5加密处理
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    // File system has file name length limit, we need to check if ext is too long, we don't add it to the filename
    if (ext.length > SD_MAX_FILE_EXTENSION_LENGTH) {
        ext = nil;
    }
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
    return filename;
}

当内存警告、进入后台、杀死进程,根据缓存时间清理过期磁盘缓存

- (void)removeExpiredData {
    //disk路径
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    
    //获取Content的内容修改日期key
    NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
    //默认是修改日期 SDImageCacheConfigExpireTypeModificationDate
    switch (self.config.diskCacheExpireType) {
        case SDImageCacheConfigExpireTypeAccessDate:
            cacheContentDateKey = NSURLContentAccessDateKey;
            break;
            
        case SDImageCacheConfigExpireTypeModificationDate:
            cacheContentDateKey = NSURLContentModificationDateKey;
            break;
            
        default:
            break;
    }
    
    NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
    
    //获取当前diskCacheURL路径下,所有的resourceKeys对应的值,并且过滤隐藏文件
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];
    
    //默认是一周,获取到现在为止过期时间节点
    NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
    NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
    NSUInteger currentCacheSize = 0;
    
    //移除比过期时间节点还老的文件
    NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
    for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
        
        if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }
        
        //获取url路径下文件的修改日期
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }
        
        //记录当前磁盘中的文件大小
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;
    }
    
    //开始移除指定路径下的文件
    for (NSURL *fileURL in urlsToDelete) {
        [self.fileManager removeItemAtURL:fileURL error:nil];
    }
    
    //如果磁盘占用的大小超过了磁盘占用的上线maxDiskSize,又会对磁盘清理
    NSUInteger maxDiskSize = self.config.maxDiskSize;
    if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
        // Target half of our maximum cache size for this cleanup pass.
        const NSUInteger desiredCacheSize = maxDiskSize / 2;
        
     
        //按照修改顺序对文件进行排序
        //oldest first
        NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                     return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                 }];
        
        //删除旧文件
        for (NSURL *fileURL in sortedFiles) {
            if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }
}
//磁盘总大小
- (NSUInteger)totalSize {
    NSUInteger size = 0;
    //文件的地址数组
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    for (NSString *fileName in fileEnumerator) {
        NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
        //获取当前文件地址下的attributes属性字典
        NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil];
        size += [attrs fileSize];
    }
    return size;
}
//总的文件个数
- (NSUInteger)totalCount {
    NSUInteger count = 0;
    //文件的地址数组
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    count = fileEnumerator.allObjects.count;
    return count;
}

SDWebImageDownloader

SDWebImageDownloader是一个单例
(1)、定义了下载队列NSOperationQueue *downloadQueue,以及根据下载的优先级添加到NSOperation *lastAddedOperation

(2)、生成SDWebImageDownloaderOperation下载的实例对象,放到下载队列中,并实现相应的NSURLSessionDataDelegate 代理方法,接收数据,并将数据传输到相应的operation类中处理

(3)、请求header的添加接口、取消下载任务等

//下载枚举值
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    /**
     //下载任务的优先级
     */
    SDWebImageDownloaderLowPriority = 1 << 0,
    
    /**
     //浏览器样式的图片下载
     */
    SDWebImageDownloaderProgressiveLoad = 1 << 1,

    /**
     *默认情况下,http请求阻止使用NSURLCache对象。如果设置了这个标记,则NSURLCache会被http请求使用。
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    /**
     *如果image/imageData是从NSURLCache返回的。则completion这个回调会返回nil
     */
    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    
    /**
     *如果app进入后台模式,是否继续下载。这个是通过在后台申请时间来完成这个操作。如果指定的时间范围内没有完成,则直接取消下载。
     */
    SDWebImageDownloaderContinueInBackground = 1 << 4,

    /**
     * 处理缓存在`NSHTTPCookieStore`对象里面的cookie。通过设置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`来实现的。
     */
    SDWebImageDownloaderHandleCookies = 1 << 5,

    /**
     * 允许非信任的SSL证书请求。
     * 在测试的时候很有用。但是正式环境要小心使用。
     */
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    /**
     * 默认情况下,图片加载的顺序是根据加入队列的顺序加载的。但是这个标记会把任务加入队列的最前面。
     */
    SDWebImageDownloaderHighPriority = 1 << 7,
    
    /**
     * 默认情况下,图片会按照他的原始大小来解码显示。这个属性会调整图片的尺寸到合适的大小根据设备的内存限制。
     * 如果`SDWebImageProgressiveDownload`标记被设置了,则这个flag不起作用。
     */
    SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
    
    /**
     * 加载图片时,图片的解压缩操作再子线程处理转化成bitmap再进行绘制
     */
    SDWebImageDownloaderAvoidDecodeImage = 1 << 9,
    
    /**
     * 只截取第一帧
     */
    SDWebImageDownloaderDecodeFirstFrameOnly = 1 << 10,
    
    /**
     * 默认情况下,对于“SDAnimatedImage”,我们在渲染时解码动画图像帧,以减少内存使用。但是,当大量imageViews共享动画图像时,可以指定将所有帧预加载到内存中,以减少CPU使用。这将在后台队列中触发“preloadAllAnimatedImageFrames”(仅限磁盘缓存和下载)
     */
    SDWebImageDownloaderPreloadAllFrames = 1 << 11
};

FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSNotificationName _Nonnull const SDWebImageDownloadFinishNotification;

typedef SDImageLoaderProgressBlock SDWebImageDownloaderProgressBlock;
typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;

/**
 *异步下载
 */
@interface SDWebImageDownloader : NSObject

/**
 * 下载的配置,包括最大并发、超时时间等
 */
@property (nonatomic, copy, readonly, nonnull) SDWebImageDownloaderConfig *config;

/**
* `SDWebImageContextDownloadRequestModifier` context修改原始的下载request
 */
@property (nonatomic, strong, nullable) id<SDWebImageDownloaderRequestModifier> requestModifier;

/**
 * 定义NSURLSession的configuration
 */
@property (nonatomic, readonly, nonnull) NSURLSessionConfiguration *sessionConfiguration;

/**
 *下载队列的suspension状态
 */
@property (nonatomic, assign, getter=isSuspended) BOOL suspended;

/**
 * 当前的下载任务数量
 */
@property (nonatomic, assign, readonly) NSUInteger currentDownloadCount;

/**
 *  初始化当前的下载
 */
@property (nonatomic, class, readonly, nonnull) SDWebImageDownloader *sharedDownloader;

/**
* 依据SDWebImageDownloaderConfig初始化
 */
- (nonnull instancetype)initWithConfig:(nullable SDWebImageDownloaderConfig *)config NS_DESIGNATED_INITIALIZER;

/**
* 添加一个HTTP header
 */
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;

/**
 * 返回一个 HTTP 头部信息
 */
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;

/**
 *创建一个异步下载任务
 *在代理方法中会暴露给外界图片下载完成或者报错 
 *
 *
 * @param url            url
 * @param options        下载的options
 * @param context        之前讲过的context字典,key可以有很多种业务场景,value是我们之前规定好的几种类型 
 * @param progressBlock 当下载过程中会根据progress的刷新间隔来设置progressBlock回调间隔
 *                       @并不是在主队列,需要放到主队列刷新
 * @param completedBlock 下载完成的回调block
 *
 * @return  token (SDWebImageDownloadToken) 可以取消当前的下载operation任务
 */
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

/**
 * 取消所有的下载任务
 */
- (void)cancelAllDownloads;

@end

.m中的主要方法实现

//设置header中的key value
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
    if (!field) {
        return;
    }
    SD_LOCK(self.HTTPHeadersLock);
    [self.HTTPHeaders setValue:value forKey:field];
    SD_UNLOCK(self.HTTPHeadersLock);
}
//回去当前header中的value
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
    if (!field) {
        return nil;
    }
    SD_LOCK(self.HTTPHeadersLock);
    NSString *value = [self.HTTPHeaders objectForKey:field];
    SD_UNLOCK(self.HTTPHeadersLock);
    return value;
}
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
            completedBlock(nil, nil, error, YES);
        }
        return nil;
    }
    
    //添加信号量
    SD_LOCK(self.operationsLock);
    //获取当前的operation
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
    //如果发现operation为nil/完成/取消,但是并没有从当前的urlOperations中移除
    if (!operation || operation.isFinished || operation.isCancelled) {
        //创建一个当前任务的operation请求
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        if (!operation) {
            SD_UNLOCK(self.operationsLock);
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
                completedBlock(nil, nil, error, YES);
            }
            return nil;
        }
        @weakify(self);
        operation.completionBlock = ^{
            @strongify(self);
            if (!self) {
                return;
            }
            SD_LOCK(self.operationsLock);
            //请求完成之后再当前的URLOperations中移除
            [self.URLOperations removeObjectForKey:url];
            SD_UNLOCK(self.operationsLock);
        };
        self.URLOperations[url] = operation;
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        //添加到队列中
        [self.downloadQueue addOperation:operation];
    }
    else if (!operation.isExecuting) {
        //还没有执行,那么修改其优先级
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        } else {
            operation.queuePriority = NSOperationQueuePriorityNormal;
        }
    }
    SD_UNLOCK(self.operationsLock);
    
    //包含进度和complete的字典
    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    
    //包装token,包含url、request、downloader、downloadOperationCancelToken等
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    token.downloader = self;
    
    return token;
}

/**
 创建一个SDWebImageDownloaderOperation对象来做下载操作,指定缓存策略、cookie策略、自定义请求头域等

 @param url url
 @param options 下载options
 @param context 自定义的context
 @return 返回一个SDWebImageDownloaderOperation,管理一个下载请求
 */
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options
                                                                                  context:(nullable SDWebImageContext *)context {
    NSTimeInterval timeoutInterval = self.config.downloadTimeout;
    if (timeoutInterval == 0.0) {
        timeoutInterval = 15.0;
    }
    
    // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
    //请求的缓存策略
    //如果设置了SDWebImageDownloaderUseNSURLCache那么NSURLRequestUseProtocolCachePolicy
    //否则忽略request中的缓存 NSURLRequestReloadIgnoringLocalCacheData
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    mutableRequest.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    mutableRequest.HTTPShouldUsePipelining = YES;
    //请求头的设置
    SD_LOCK(self.HTTPHeadersLock);
    mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
    SD_UNLOCK(self.HTTPHeadersLock);
    
    //获取外部提供的requestModifier
    id<SDWebImageDownloaderRequestModifier> requestModifier;
    if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
        requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
    } else {
        requestModifier = self.requestModifier;
    }
    
    NSURLRequest *request;
    if (requestModifier) {
        //返回一个外部设置好的request信息
        NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
        // If modified request is nil, early return
        if (!modifiedRequest) {
            return nil;
        } else {
            request = [modifiedRequest copy];
        }
    } else {
        request = [mutableRequest copy];
    }
    
    //获取SDWebImageDownloaderOperation类对象
    Class operationClass = self.config.operationClass;
    if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
        // Custom operation class
    } else {
        operationClass = [SDWebImageDownloaderOperation class];
    }
    //获取一个operation对象
    NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
    
    if ([operation respondsToSelector:@selector(setCredential:)]) {
        //配置验证信息
        if (self.config.urlCredential) {
            //ssl验证
            operation.credential = self.config.urlCredential;
        } else if (self.config.username && self.config.password) {
            //用户名密码验证
            operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
        }
    }
        
    if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
        //progress的时间间隔
        operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
    }
    
    //operation的优先级
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    
    if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
        //如果是LIFO这种模式,则让前面的operation依赖于最新添加的operation
        [self.lastAddedOperation addDependency:operation];
        self.lastAddedOperation = operation;
    }
    
    return operation;
}
//取消某一个请求
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
    NSURL *url = token.url;
    if (!url) {
        return;
    }
    SD_LOCK(self.operationsLock);
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
    if (operation) {
        //NSOperation取消下载任务
        BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
        if (canceled) {
            [self.URLOperations removeObjectForKey:url];
        }
    }
    SD_UNLOCK(self.operationsLock);
}

在内部还有一些网络请求的代理方法,这些代理方法在接收数据之后,在当前的operations中进行查找当前的下载任务,查找到之后放到当前SDWebImageDownloaderOperation中进行处理这些请求到的数据,代码就不显示了,大家可以自己看一下比较简单

SDWebImageDownloaderConfig

在配置类中设置相应的网络请求配置,默认生成的类是一个单例,包含超时时间、最大并发数、ssl证书等网络请求的配置项,都保存到当前类中。

/**
 //单例,默认下载配置最多6个并发,15s超时
 */
@property (nonatomic, class, readonly, nonnull) SDWebImageDownloaderConfig *defaultDownloaderConfig;

/**
 //下载的最大并发数6个
 */
@property (nonatomic, assign) NSInteger maxConcurrentDownloads;

/**
 //下载的超时时间 默认15s
 */
@property (nonatomic, assign) NSTimeInterval downloadTimeout;

/**
 //最小界面progress的刷新间隔
 //当设置progressive decoding feature,我们需要展示进度,该属性表示刷新间隔
 */
@property (nonatomic, assign) double minimumProgressInterval;

/**
 //NSURLSession的默认配置,当下载开始之后不支持动态修改
 */
@property (nonatomic, strong, nullable) NSURLSessionConfiguration *sessionConfiguration;

/**
 //SDWebImageDownloaderOperation下载类
 */
@property (nonatomic, assign, nullable) Class operationClass;

/**
 //图片的下载执行顺序,默认是先进先出,SDWebImageDownloaderFIFOExecutionOrder
 */
@property (nonatomic, assign) SDWebImageDownloaderExecutionOrder executionOrder;

/**
 //默认是nil,为图片加载request设置一个ssl证书对象
 */
@property (nonatomic, copy, nullable) NSURLCredential *urlCredential;

/**
 //默认是nil,为http的Basic 认证设置用户名
 */
@property (nonatomic, copy, nullable) NSString *username;

/**
 //默认是nil,为http的Basic 认证设置密码
 */
@property (nonatomic, copy, nullable) NSString *password;

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation继承自NSOperation

(1)、并行执行NSOperation,管理executing,finished等各种属性的处理,手动触发KVO。

(2)、重写start、cancel方法用来触发网络请求以及取消请求

(3)、在NSURLSessionDataDelegate等代理方法中对数据进行加载,并验证当前的https请求是否有效,获取数据之后的数据拼接处理并回调block,发送请求数据状态的通知

- (void)setFinished:(BOOL)finished {
    //手动触发finish
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing {
    //手动触发isExecuting
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

/并行处理的Operation需要重写这个方法,在这个方法里具体的处理
- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

#if SD_UIKIT
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        //如果进入后台,
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak typeof(self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                //后台执行结束之后,做取消
                [wself cancel];
            }];
        }
#endif
        //如果在downloader中的session被设置为nil,需要我们生成一个ownSession
        NSURLSession *session = self.unownedSession;
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;
        }
        
        //获得当前的图片缓存,为以后的数据核验是否需要缓存更新作准备
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // Grab the cached data for later check
            NSURLCache *URLCache = session.configuration.URLCache;
            if (!URLCache) {
                URLCache = [NSURLCache sharedURLCache];
            }
            NSCachedURLResponse *cachedResponse;
            // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
            @synchronized (URLCache) {
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
            }
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }

    //设置当前的任务优先级
    if (self.dataTask) {
        if (self.options & SDWebImageDownloaderHighPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityHigh;
        } else if (self.options & SDWebImageDownloaderLowPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityLow;
        }
        //执行当前的任务
        [self.dataTask resume];
        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:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
        [self done];
    }
}
//取消任务
- (void)cancel {
    @synchronized (self) {
        [self cancelInternal];
    }
}

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel];

    if (self.dataTask) {
        [self.dataTask cancel];
        __block typeof(self) strongSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
        });

        // As we cancelled the task, its callback won't be called and thus won't
        // maintain the isFinished and isExecuting flags.
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }

    [self reset];
}

- (void)done {
    self.finished = YES;
    self.executing = NO;
    [self reset];
}

- (void)reset {
    SD_LOCK(self.callbacksLock);
    [self.callbackBlocks removeAllObjects];
    SD_UNLOCK(self.callbacksLock);
    
    @synchronized (self) {
        self.dataTask = nil;
        
        if (self.ownedSession) {
            [self.ownedSession invalidateAndCancel];
            self.ownedSession = nil;
        }
        
#if SD_UIKIT
        if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
            // If backgroundTaskId != UIBackgroundTaskInvalid, sharedApplication is always exist
            //结束app的后台任务
            UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
            [app endBackgroundTask:self.backgroundTaskId];
            self.backgroundTaskId = UIBackgroundTaskInvalid;
        }
#endif
    }
}

看一下代理方法中的执行逻辑,首先判断当前的https证书是否通过验证,以及接受到请求之后是否允许当前的网络请求,如果允许那么开始接收数据(频繁调用),最终数据请求结束

#pragma mark NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    //总长度
    NSInteger expected = (NSInteger)response.expectedContentLength;
    expected = expected > 0 ? expected : 0;
    self.expectedSize = expected;
    self.response = response;
    //状态码
    NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
    BOOL valid = statusCode >= 200 && statusCode < 400;
    if (!valid) {
        self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
    }
    //'304 Not Modified' is an exceptional one
    //URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check
    //如果返回304,但是没有缓存数据,返回错误
    if (statusCode == 304 && !self.cachedData) {
        valid = NO;
        self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
    }
    
    if (valid) {
        //如果是有效的数据,那么返回当前进度
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, expected, self.request.URL);
        }
    } else {
        // Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle
        disposition = NSURLSessionResponseCancel;
    }
    //接收到数据的通知
    __block typeof(self) strongSelf = self;
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:strongSelf];
    });
    
    //允许接收数据
    if (completionHandler) {
        completionHandler(disposition);
    }
}
//接收到下载的网络数据之后的处理
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    if (!self.imageData) {
        self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
    }
    //收集图片nsdata
    [self.imageData appendData:data];
    
    self.receivedSize = self.imageData.length;
    if (self.expectedSize == 0) {
        //假如不知道期望图片的size,立刻返回
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
        }
        return;
    }
    
    // Get the finish status
    //接收的数据大于等于期望的大小,说明获取图片成功
    BOOL finished = (self.receivedSize >= self.expectedSize);
    // Get the current progress
    //获取当前的进度
    double currentProgress = (double)self.receivedSize / (double)self.expectedSize;
    double previousProgress = self.previousProgress;
    double progressInterval = currentProgress - previousProgress;
    // Check if we need callback progress
    //假如没有下载完成&&下载进度小于最小进度
    if (!finished && (progressInterval < self.minimumProgressInterval)) {
        return;
    }
    //以前的进度就等于当前进度
    self.previousProgress = currentProgress;
   
    //并且当前是按照SDWebImageDownloaderProgressiveLoad来展示图片
    if (self.options & SDWebImageDownloaderProgressiveLoad) {
        // Get the image data
        NSData *imageData = [self.imageData copy];
        
        // progressive decode the image in coder queue
        //进行异步解压缩操作
        dispatch_async(self.coderQueue, ^{
            //解压缩过程中可能会产生很多的中间变量,消耗内存所以放到自动释放池,runloop到before waiting清理
            @autoreleasepool {
                //解压缩
                UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
                if (image) {
                    // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
                    
                    [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
                }
            }
        });
    }
    
    for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
        progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
    
    NSCachedURLResponse *cachedResponse = proposedResponse;
    
    if (!(self.options & SDWebImageDownloaderUseNSURLCache)) {
        // Prevents caching of responses
        cachedResponse = nil;
    }
    //根据request选项,决定是否缓存NSCachedURLResponse
    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

#pragma mark NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    @synchronized(self) {
        self.dataTask = nil;
        __block typeof(self) strongSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
            if (!error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:strongSelf];
            }
        });
    }
    
    // make sure to call `[self done]` to mark operation as finished
    //标记完成了
    if (error) {
        // custom error instead of URLSession error
        if (self.responseError) {
            error = self.responseError;
        }
        [self callCompletionBlocksWithError:error];
        [self done];
        
    } else {
        if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
            //下载完成,将本地的imageData置为nil,防止下次进入数据出错
            NSData *imageData = [self.imageData copy];
            self.imageData = nil;
            if (imageData) {
                /**  if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
                 *  then we should check if the cached data is equal to image data
                 */
                //假如只用缓存的数据,并且缓存数据等于当前imagedata那么返回error
                //这一步是判断是否进行缓存刷新的关键
                if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
                    self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
                    // call completion block with not modified error
                    [self callCompletionBlocksWithError:self.responseError];
                    [self done];
                } else {
                    // decode the image in coder queue
                    //在coderQueue中处理图片的异步
                    dispatch_async(self.coderQueue, ^{
                        @autoreleasepool {
                            UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
                            CGSize imageSize = image.size;
                            if (imageSize.width == 0 || imageSize.height == 0) {
                                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                            } else {
                                [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
                            }
                            [self done];
                        }
                    });
                }
            } else {
                [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
                [self done];
            }
        } else {
            [self done];
        }
    }
}
//验证https证书
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    //使用可以信任证书颁发机构的证书
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        //如果设置了SDWebImageDownloaderAllowInvalidSSLCertificates,则信任任何证书,不需要验证
        if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        } else {
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            disposition = NSURLSessionAuthChallengeUseCredential;
        }
    } else {
        //自己生成的证书
        if (challenge.previousFailureCount == 0) {
            if (self.credential) {
                credential = self.credential;
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    //返回验证结果
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

欢迎关注我的公众号,专注iOS开发、大前端开发、跨平台技术分享。


iOS开发之家
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 下载 下载管理器 SDWebImageDownLoader作为一个单例来管理图片的下载操作。图片的下载是放在一个N...
    wind_dy阅读 1,445评论 0 1
  • 技术无极限,从菜鸟开始,从源码开始。 由于公司目前项目还是用OC写的项目,没有升级swift 所以暂时SDWebI...
    充满活力的早晨阅读 12,622评论 0 2
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 项目中一直都有使用SDWebImage,对这个框架有一定的了解,但是体系却未能贯通,因此特地整理下,主要参考: i...
    林大鹏阅读 1,457评论 2 13
  • 今天吃过晚饭妈妈带我还有邻居家的哥哥去体育场玩了,到了广场人好多啊!有跳舞的,舞剑的,打太极的,还有跑步的,我在想...
    衡越阅读 371评论 0 0