看了下离开上次写简书博客的大运,已经过去了多少个多月了,很惭愧。正好近日项目不忙,抽点时间商讨下第3方库,朋友提议统计写成博客就这么开篇了。内容篇幅会相比较长,所以希望各位看官搬好小板凳看SDWebImage源码解析,如果没有定性真的是很难坚定不移下去。希望大家能够百折不回跟着博主一块学完SDWebImage源码体系。

爱博体育 1

一.预备知识

在规范学习源码前,先讲一些SDWebImage中用到的生僻知识点,有些用的很频仍,可是众三人对这个知识点模糊不清,倘使不搞清楚会大大影响阅读功能,比如枚举NS_OPTIONS的二进制位运算。

年也过完了、决定补完一下出道时就欠下的债。

1> NS_OPTIONS与位运算

NS_OPTIONS用来定义位移相关操作的枚举值,当三个枚举变量须求率领二种值的时候就必要,大家得以参考UIKit.Framework的头文件,可以观看大批量的枚举定义。例如在SDWebImage下边就会触发到SDWebImageOptions枚举值:

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,
    SDWebImageLowPriority = 1 << 1,
    SDWebImageCacheMemoryOnly = 1 << 2,
    SDWebImageProgressiveDownload = 1 << 3,
    SDWebImageRefreshCached = 1 << 4,
    SDWebImageContinueInBackground = 1 << 5,
    SDWebImageHandleCookies = 1 << 6,
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    SDWebImageHighPriority = 1 << 8,
    SDWebImageDelayPlaceholder = 1 << 9,
    SDWebImageTransformAnimatedImage = 1 << 10,
    SDWebImageAvoidAutoSetImage = 1 << 11,
    SDWebImageScaleDownLargeImages = 1 << 12
};

“<<”是位运算中的左移运算符,第多个值SDWebImageRetryFailed = 1
<<
0,十进制1转化为二进制:0b00000001,那里<<0将全数二进制位左移0位,那么如故0b00000001,最后SDWebImageRetryFailed
值为1.
第一个枚举值SDWebImageLowPriority
=1<<1,那里是将1的二进制全数位向左移动一个人,空缺的用0补齐,那么0b00000001变成0b00000010,十进制为2则SDWebImageLowPriority值为2。

左移壹人示意图

逐一类推:
SDWebImageCacheMemoryOnly向左移动2个人等于4,
SDWebImageProgressiveDownload向左移动几人等于8.
上边写一个,customImageView是我们自定义的imageView实例,在SDWebImage的SDWebImageManager.m具体接纳中:

   [customImageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];

留神到代码中用到了”|”,‘|’是位运算中的或运算,需求七个操作数,功用是将五个数的等同位举办逻辑或运算,即只要三个对应位有二个位1,则运算后此位为1,若是五个对应位都为0。例如十进制1的二进制0b00000001
| 十进制2的二进制0b00000010,结果为0b00000011十进制为3。下图示例:

或运算

当options值为SDWebImageRetryFailed |
SDWebImageCacheMemoryOnly时,执行或运算0b00000001| 0b00000100 =
0b00000101 十进制是5.
那么在具体的不二法门内部options怎么利用呢?上面的代码SD将options和SDWebImageRetryFailed做”&”运算:

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
    }

‘&’是位运算中的与运算,当对应位数同为1结出才为1.诸如十进制1的二进制0b00000001&十进制2的二进制0b00000010,结果为0b00000000十进制是0.

与运算

上面代码中,SD将那儿的options二进制0b00000101和SDWebImageRetryFailed的二进制举办&运算,假诺options包括了SDWebImageRetryFailed则结果为真。SD通篇都基于options做了过多事务,由此了然可选枚举和位运算很是主要。

参拜一下SDWebImage的源码。

并不是说肯定要读什么如何、只是觉得源码的开卷是一种很好的求学方法。无论从架构还是技术点方面。


2> NSURLCredential

当移动端和服务器在传输进程中,服务端有或者在回来Response时顺手表明,询问
HTTP
请求的发起方是哪个人,那时候发起方应提供科学的用户名和密码(即认证音信)。那时候就需求NSU凯雷德LCredential身份表明,特别具体能够查看这篇博客

目录

  • 常见疑难(面试大全?)
    • 磁盘目录位于哪个地方?
    • 最大并发数、超时时长?
    • 图表怎么样命名?
    • 怎样分辨图片类型?
    • 所查找到的图纸的发源?
    • 有着下载的图样都将被写入缓存?磁盘呢?曾几何时缓存的?
    • 磁盘缓存的时长?清理操作的日子点?
    • 磁盘清理的准绳?
    • 下载图片时、会采取缓存协议么?
    • 下载图片的UPRADOL必须是NSU奥迪Q7L么?
    • 读取缓存以及读取磁盘的时候怎么着保管线程安全?
  • 连锁知识点
    • NS_OPTIONS枚举与位运算
    • 内联函数
  • 常备不懈工作
  • 行事规律
  • 事务层级
  • 着力代码(平常读取下载图片)
    • 最上层:UIView+WebCache
    • 逻辑层:SDWebImageManager
    • 业务层:
      • 缓存&&磁盘操作(SDImageCache)
      • 下载操作(SDWebImageDownloader)
  • 一对启示
    • 分层的接口API设计
    • 爱博体育,线程安全
    • 内联函数
    • 鬼斧神工的缓存管理原则
    • 回调设计

3>涉及的宏定义

普遍疑难(面试大全?)

就算自个儿更推荐阅读源码、可假如实际没时间。这一段只要开销几分钟。
本人要么对比喜欢把干货放在前方、方便伸手党(比如本身)。
不过也不大概保险涵盖全数题材、欢迎留言。

  • #### 磁盘目录位于哪儿?

缓存在磁盘沙盒目录下 Library/Caches
二级目录为~/Library/Caches/default/com.hackemist.SDWebImageCache.default

- (instancetype)init {
    return [self initWithNamespace:@"default"];
    //   ~Library/Caches/default
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns]

        // Init the disk cache
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

//  _diskCachePath = ~/Library/Caches/default/com.hackemist.SDWebImageCache.default
}

您也足以通过[[SDImageCache sharedImageCache] addReadOnlyCachePath:bundledPath];源于定义二个门道。

3-1>FOUNDATION_EXPORT:

用来定义常量,和#define作用一样,只不过在检测字符串的值是或不是等于是比#define的频率更高,因为正如的是指针地址。

但以此路子不会被积存使用、是给开发者自定义预装图片的路径。
  • #### 最大并发数、超时时长?

_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadTimeout = 15.0;
  • #### 图片如何命名?

那里写入缓存和写入磁盘是分歧的。
写入缓存时、直接用图片url作为key

//写入缓存
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];

写入磁盘时、用url的MD5编码作为key。能够免患文件名过长

- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    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;
    //key == https://gss2.bdstatic.com/-fo3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=034361ab922397ddc274905638ebd9d2/d31b0ef41bd5ad64dddebb.jpg;
    //filename == f029945f95894e152771806785bc4f18.jpg;
}
  • #### 如何鉴别图片类型?

透过NSData数据的首先个字符举办判定。

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }

    // File signatures table: http://www.garykessler.net/library/file_sigs.html
    uint8_t c;
    [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;
                }
            }
            break;
        }
    }
    return SDImageFormatUndefined;
}
  • #### 所查找到的图样的源于?

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * 从网上下载
    */
    SDImageCacheTypeNone,
    /**
     * 从磁盘获得
     */
    SDImageCacheTypeDisk,
    /**
     * 从内存获得
     */
    SDImageCacheTypeMemory
};
  • #### 全体下载的图形都将被写入缓存?磁盘呢?曾几何时缓存的?

磁盘不是强制写入。从枚举SDWebImageOptions可见

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {

    /**
     *  禁用磁盘缓存
     */
    SDWebImageCacheMemoryOnly = 1 << 2,
}

而Memory缓存应该是必须写入的(因为本人并没找到何地可以禁止)。
缓存的时间点、有多个(开发者也得以积极缓存)、且皆以由SDWebImageManager进行。
其一是下载成功后、自动保存。或许开发者通过代理处理图片并赶回后缓存

- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;


=========>>SDWebImageManager
//获取转换用户后的图片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

//用户处理成功
if (transformedImage && finished) {
      BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];

      //用户处理的后若未生成新的图片、则保存下载的二进制文件。
      //不然则由imageCache内部生成二进制文件保存
      [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}

那多少个是当缓存中尚无、不过从硬盘中查询到了图片。

@autoreleasepool {
        //搜索硬盘
        NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        UIImage *diskImage = [self diskImageForKey:key];
        //缓存到内存、默认为YES
        if (diskImage && self.config.shouldCacheImagesInMemory) {
             NSUInteger cost = SDCacheCostForImage(diskImage);
             //使用NSChache缓存。
             [self.memCache setObject:diskImage forKey:key cost:cost];
        }
       if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
               doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
             });
        }
}
  • #### 磁盘缓存的时长?清理操作的岁月点?

3-2>NS_DESIGNATED_INITIALIZER :

NS_DESIGNATED_INITIALIZELAND宏来完成内定构造器,日常是想告知调用者要用这一个点子去开端化类对象,便于规范API。

私行认同为一周
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

可以以时间清除磁盘的章程为

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

调用的机遇为

[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
 [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];

约等于当程序退出到后台、可能被杀掉的时候。
此地、还有此外3个点。
Long-Running Task任务

- (void)backgroundDeleteOldFiles {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    //后台任务标识--注册一个后台任务
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        //超时(大概150秒?)自动结束后台任务
        //结束后台任务
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];


    [self deleteOldFilesWithCompletionBlock:^{

        //结束后台任务
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

好端端程序在进入后台后、固然可以继续执行职分。但是在岁月不长内就会被挂起待机。
Long-Running可以让系统为app再多分配一些时间来拍卖部分耗时职分。

  • #### 磁盘清理的规格?

首先、通过时间举行清理。(最后修改时间>十一日)
接下来、依照占据内存大小进行清理。(假如占据内存大于上限、则按时间排序、删除到上限的十分之五。)
那里本身并不曾观望接纳功能优先级判断、所以理应是没有。

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    //异步清理超时图片
    dispatch_async(self.ioQueue, ^{
        //获取磁盘目录
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        //NSURLIsDirectoryKey 判断是否为目录
        //NSURLContentModificationDateKey 判断最后修改时间
        //NSURLTotalFileAllocatedSizeKey 判断文件大小
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        //模具器--遍历磁盘路径下的文件
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //计算一周前(需要释放)、的时间
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        //保存缓存文件Dic
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        //缓存总大小
        NSUInteger currentCacheSize = 0;
        //需要删除的url路径
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        //遍历磁盘文件枚举器
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            //获取每个文件所对应的三个参数(resourceKeys)
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

            // Skip directories and errors.
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                //如果是文件夹则跳过
                continue;
            }

            // Remove files that are older than the expiration date;
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                //如果时间超过指定日期、加入删除数组。跳过
                [urlsToDelete addObject:fileURL];
                continue;
            }
            //获取文件大小、并且把路径与大小存入字典。
            // Store a reference to this file and account for its total size.
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }

        //遍历删除文件
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        //如果剩余文件大小仍超过阈值
        //优先删除最老的文件
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

            // 将剩余的文件按修改时间排序
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                                     }];

            // 删除文件
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                    //直到低于阈值的二分之一
                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        //回调给主线程
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}
  • #### 下载图片时、会使用互连网协议缓存逻辑么?

暗许情状下不会、由以下代码可知。

NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];

除非将options配置成SDWebImageDownloaderUseNSURLCache、否则每趟都会从原地点重新下载、而不是用网络协议的缓存逻辑。

  • #### 下载图片的U途乐L必须是NSUENCOREL么?

不是、在SDWebImageManager中有过容错处理。所以即便你传入三个字符串、依然可以正确的查找。

if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

可是由于API暴光出的是(nullable NSURL *)、假设你传入字符串、会有宝石蓝警告


  • #### 读取缓存以及读取磁盘的时候怎样确保线程安全?

  • 读取缓存
    读取缓存的时候是在主线程进行。由于应用NSCache进行仓储、所以不须要担心单个value对象的线程安全。

  • 读取磁盘
    磁盘的读取纵然创造了三个NSOperation对象、但据笔者所见那几个目的只是用来标记该操作是还是不是被撤销、以及裁撤之后不再读取磁盘文件的机能。
    真的的磁盘缓存是在另四个IO专属线程中的一个串行队列下展开的。
    假若您追寻self.ioQueue还是可以发现、不只是读取磁盘内容。
    包罗删除、写入等富有磁盘内容都以在这么些IO线程举办、以确保线程安全。
    但计算大小、获取文件总数等操作。则是在主线程举办。

_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
==========>>>>><<<<<<===========
NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //搜索硬盘
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            //缓存到内存、默认为YES
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //使用NSChache缓存。
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });
    return operation;

3-2>__deprecated_msg

用来指示此办法或质量已经丢掉。

@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");

连锁知识点

如果对一些知识点不打听、恐怕对代码精通造成困扰。列举一下。

  • #### NS_OPTIONS枚举与位运算

上文中的SDWebImageOptions便是多个位移枚举

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,
    SDWebImageLowPriority = 1 << 1,
    SDWebImageCacheMemoryOnly = 1 << 2,        
    SDWebImageProgressiveDownload = 1 << 3,
    SDWebImageRefreshCached = 1 << 4,
    SDWebImageContinueInBackground = 1 << 5,
    SDWebImageHandleCookies = 1 << 6,
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    SDWebImageHighPriority = 1 << 8,
    SDWebImageDelayPlaceholder = 1 << 9,
    SDWebImageTransformAnimatedImage = 1 << 10,
    SDWebImageAvoidAutoSetImage = 1 << 11,
    SDWebImageScaleDownLargeImages = 1 << 12
};

和我们普通用的枚举

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    SDImageCacheTypeNone,
    SDImageCacheTypeDisk,
    SDImageCacheTypeMemory
};

从外表看有两点差距:

  • 枚举表明:NS_ENUM&& NS_OPTIONS
    实际从概念的职能上来讲、二者作用一样。
    更加多的是语义化的角度。前者是普通枚举、后者是位移枚举
  • 枚举中的位运算符号<<.
    位运算中、有三种为主运算符号.
  • ##### 按位与”&”

除非对应的多个二进位均为1时,结果位才为1,否则为0
诸如9&5,其实就是1001&0101=0001,因而9&5=1>二进制中,与1相&就保障原位,与0相&就为0

  • ##### 按位或”|”

借使对应的1个二进位有一个为1时,结果位就为1,否则为0。
例如9|5,其实就是1001|0101=1101,因而9|5=13

  • ##### 左移”<<“

把整数a的各二进位一体左移n位,高位放任,低位补0。左移n位其实就是乘以2的n次方。
例如1<<2 就是0001左移2为0100,因此1<<2=4

4>initialize

initialize静态方法会在第⑥次采用该类以前由运维期系统调用,而且仅调用几回,属于懒加载范畴,假诺不应用则不会调用,可以在章程内部做一些开始化操作,可是load方法是只要开动程序就会调用。关于initialize和load尤其详细的看这里

于是、在使用位移枚举的时候、我们就有了那种写法:
options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];

下边的情致是。那一个操作是如若退步了索要重试、并且只写入缓存。
其中 options=SDWebImageRetryFailed | SDWebImageCacheMemoryOnly
也就是0b00000001| 0b00000100 = 0b00000101 十进制中 = 5.

5>dispatch_barrier_sync

GCD中的知识点,承上启下,当把义务A添加到队列中选择dispatch_barrier_sync时,它会等待在它面前插入队列的义务先进行完,然后等任务履行完再进行后边的天职。越发详实的可点这里

脚下打点了这几个,假诺还有其余须要讲的话,下文子禽再做表明。大家有目生的知识点也足以在说长道短中还原,小编会挑痛点相比多的立时更新到博客中。

在其间判断时候就有了之类写法:
//是否磁盘缓存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

等价于 0101 & 0100 = 0100 结果为真。
倘若

BOOL lowPriority = !(options & SDWebImageLowPriority);

等价于 0101 & 0010 = 0000 结果为假。

  • ### 内联函数

在写入缓存时、出现了那样一行代码

NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];

里面SDCacheCostForImage指向多个静态内联函数

FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

其中FOUNDATION_STATIC_INLINE作为宏指向static inline、所以也相当于

static __inline__ NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

用宏写方法、大家都用过。不过表达式形式的宏定义有一定的流弊。(比如参数检查、越界等等)。

二.宗旨源码解析

内联函数完全可以取代表达式方式的宏定义。

附带谈谈为啥要用内联函数吧。

  • 频率来看
    • 函数之间调用,是内存地址之间的调用、当函数调用落成之后还会回到原来函数执行的地点。函数调用将会有时间支付。
    • 内联函数在汇编中尚无call语句。撤消了函数的参数压栈
  • 相对而言表明式格局的宏定义
    • 亟待预编译.因为inline内联函数也是函数、不需要预编译。
    • 调用时候会率先检查它的参数的项目、保险调用正确。
    • 可以动用所在类的护卫成员及民用成员。

SDWebImage的核心Workflow:

SDWebImage-Workflow

大家要探讨的源码紧如果环绕那个宗旨类举行。
阅读提出TIPS:SDWebImage的源码多,逻辑复杂,作者的提出是读者下载一份源码,源码和本文同步阅读,因为只要没看过源码的调用逻辑,单看本文的解读不会形成连串。会在源码上加注释,并且copy到文章中,运营项目参考调用栈合营本文效果会更好。

内需留意的是
  • 内联函数中尽量不要使用诸如循环语句等大气代码、大概会招致编译器舍弃内联动作。
  • 内联函数的定义须在调用此前。

1>UIImageView+WebCache/UIView+WebCache

UIImageView+WebCache对外使用的API入口,这一个类的接口设计把设计形式五大条件之一的接口分离原则反映的淋漓。首先说一下什么样是接口分离原则:
接口分离原则:为特定功用提供特定的接口,不要拔取单一的总接口包涵全数机能,而是应该依照效率把那些接口分割,收缩看重,不或然迫使用户去重视这多少个他们不利用的接口。
在.h中得以看看:

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options

如此那般调用者必要什么样效果就去调用特定的API,清晰易伸张,在.m中会规划多少个总的接口包括所有功效。UIImageView+WebCache紧假如一个接口,没有太多要求研商的,在.m中总接口中又调用了UIView的伸张方法,接下去讲一下UIView+WebCache。
UIView+WebCache提供了具体的图样加载请求,UIButton和UIImageView都可调用sd_internalSetImageWithU帕杰罗L来贯彻,上面具体看下sd_internalSetImageWithURAV4L的落到实处。具体的源码意思都做了诠释:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary *)context {
    //根据参数operationKey取消当前类所对应的下载Operation对象,如果operationKey为nil key取NSStringFromClass([self class])
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //具体的取消操作在UIView+WebCacheOperation中实现
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];

    //利用关联对象给当前self实例绑定url key=imageURLKey value=url
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //这里就用到了我们开篇讲的位运算,利用&与运算判断调用者是否需要设置占位图,需要则set
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

    if (url) {
        // check if activityView is enabled or not
        // 判断之前是否利用关联对象给self设置了显示菊花加载,如果有则add
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        //调用SDWebImageManager的loadImageWithURL方法去加载图片,返回值是SDWebImageCombinedOperation
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            //在这里移除菊花
            [sself sd_removeActivityIndicator];
            if (!sself) { return; }
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否不显示图片两个条件满足其一即可 1>调用者手动主动配置,哪怕image不为nil 2>没有图片并且不delaye占位图情况
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!sself) { return; }
                if (!shouldNotSetImage) {
                    [sself sd_setNeedsLayout];
                }
                //如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, error, cacheType, url);
                }
            };

            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {//如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }

            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {//如果没有image,并且调用者设置了delaye显示默认图那这里targetImage设置为placeholder
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            BOOL shouldUseGlobalQueue = NO;
            //外部参数context如果设置了全局队列中setImage,那shouldUseGlobalQueue为YES,否则默认在dispatch_get_main_queue
            if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
                shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
            }
            dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();

            dispatch_queue_async_safe(targetQueue, ^{//队列中设置image给imageView或者button
                [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                dispatch_main_async_safe(callCompletedBlockClojure);
            });
        }];
        //绑定operation到当前self,key=validOperationKey,value=operation
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {v
        dispatch_main_async_safe(^{
            //移除菊花 抛出url为nil的回调
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

下面是在这一经过的大约流程图辅助领悟:

预备干活

随手下载了3个新式的 (4.2.3)

2>SDWebImageManager

SDWebImageManager类是SDWebImage中的核心类,主要承担调用SDWebImageDownloader举办图片下载,以及在下载之后接纳SDImageCache进行图纸缓存。并且此类还足以跳过UIImageViewe/Cache大概UIView/Cache单独使用,不仅局限于壹个UIView。
SDWebImageManager.h注解:

@class SDWebImageManager;

@protocol SDWebImageManagerDelegate <NSObject>

@optional

//当缓存没有发现当前图片,那么会查看调用者是否实现改方法,如果return一个no,则不会继续下载这张图片
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;

//当图片下载完成但是未添加到缓存里面,这时候调用该方法可以给图片旋转方向,注意是异步执行, 防止组织主线程
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

@end

@interface SDWebImageManager : NSObject
//SDWebImageManagerDelegate的delegate
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
//缓存中心
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
//下载中心
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
//这个缓存block的作用是,在block内部进行缓存key的生成并return,key就是根据图片url根据规则生成,sd的缓存策略就是key是图片url,value就是image
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
//返回SDWebImageManager的单例
+ (nonnull instancetype)sharedManager;
//根据特定的cache和downloader生成一个新的SDWebImageManager
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
//下载图片的关键方法,第一个参数图片url,第二个参数设置下载多样操作,第三个参数下载中进度block,第四个参数下载完成后回调
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                              options:(SDWebImageOptions)options
                                             progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                            completed:(nullable SDInternalCompletionBlock)completedBlock;
//缓存图片根据指定的url和image
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
//取消所有当前的operation
- (void)cancelAll;
//检查是否有图片正在下载
- (BOOL)isRunning;
//异步检查图片是否已经缓存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//检查图片是否缓存 在磁盘中
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//给定一个url返回缓存的字符串key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

@end

SDWebImageManager.m注解:

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
//是否取消当前所有操作
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
//没有参数取消回调
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
//执行缓存的操作
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end

@interface SDWebImageManager ()
//缓存对象
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
//下载对象
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
//集合存储所有下载失败的图片url
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
//存储正在执行下载图片操作的数组
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;

@end

@implementation SDWebImageManager
//生成一个SDWebImagemanager的单例
+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
//初始化SDImageCache/SDWebImageDownloade
- (nonnull instancetype)init {
    SDImageCache *cache = [SDImageCache sharedImageCache];
    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    return [self initWithCache:cache downloader:downloader];
}
//初始化以及属性绑定
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}
//根据URL获取缓存中的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
    if (!url) {
        return @"";
    }

    if (self.cacheKeyFilter) {
        return self.cacheKeyFilter(url);
    } else {
        return url.absoluteString;
    }
}
//检查缓存中是否缓存了当前url对应的图片-先判断内存缓存、再判断磁盘缓存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];
    //判断内存缓存是否存在
    BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);

    if (isInMemoryCache) {
        // making sure we call the completion block on the main queue
        dispatch_async(dispatch_get_main_queue(), ^{
            if (completionBlock) {
                completionBlock(YES);
            }
        });
        return;
    }
    //判断磁盘缓存中是否存在
    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
//根据URL判断磁盘缓存中是否存在图片
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    NSString *key = [self cacheKeyForURL:url];

    [self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
        // the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
        if (completionBlock) {
            completionBlock(isInDiskCache);
        }
    }];
}
//进行图片下载操作
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    //completedBlock为nil,则触发断言,程序crash
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    //封装下载操作的对象
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        //为了防止在多线程访问出现问题,创建互斥锁
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    //如果url为nil,或者没有设置失败url重新下载的配置且该url已经下载失败过,那么返回失败的回调
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    //创建互斥锁,添加operation到数组中
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
    //使用缓存对象,根据key去寻找查找
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        //如果当前操作被取消,则remove且return
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        //(如果没有图片缓存或者设置了重新刷新缓存)且调用代理允许下载图片
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //如果有(其实是缓存的)并且调用者设置了重新刷新缓存,那么先把图片结果回调出去,然后继续去下载图片再更新缓存
            if (cachedImage && options & SDWebImageRefreshCached) {
                // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

            // download if no image or requested to refresh anyway, and download allowed by delegate
            //如果缓存没有图片或者请求刷新,并且通过代理下载图片,那么则下载图片
            //下面是根据调用者传进来的option,来匹配设置了哪些,就给downloaderOptions赋值哪些option
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;

            if (cachedImage && options & SDWebImageRefreshCached) {
                // force progressive off if image already cached but forced refreshing
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            //在这里真正调用imageDownloader去下载图片了
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                //操作取消则不做任何处理
                if (!strongOperation || strongOperation.isCancelled) {
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost
                        && error.code != NSURLErrorNetworkConnectionLost) {
                        //下载失败则添加图片url到failedURLs集合
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //虽然下载失败,但是如果设置了可以重新下载失败的url则remove该url
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否需要缓存在磁盘
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    //图片下载成功并且判断是否需要转换图片
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //根据代理获取转换后的图片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            //如果转换图片存在且下载图片操作已完成 则在缓存对象中存储图片
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }

                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        //下载完成且有image则缓存图片
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                //如果下载和缓存都完成了则删除操作队列中的operation
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            @synchronized(operation) {
                // Need same lock to ensure cancelBlock called because cancel method can be called in different queue
                operation.cancelBlock = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            }
        } else if (cachedImage) {
            // 有图片且线程没有被取消,则返回有图片的completedBlock
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            // Image not in cache and download disallowed by delegate
            //没有在缓存中并且代理方法也不允许下载则回调失败
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}
//将图片存入缓存
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url {
    if (image && url) {
        NSString *key = [self cacheKeyForURL:url];
        [self.imageCache storeImage:image forKey:key toDisk:YES completion:nil];
    }
}
//取消所有的下载操作
- (void)cancelAll {
    @synchronized (self.runningOperations) {
        NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
        [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
        [self.runningOperations removeObjectsInArray:copiedOperations];
    }
}
//判断当前是否有下载图片
- (BOOL)isRunning {
    BOOL isRunning = NO;
    @synchronized (self.runningOperations) {
        isRunning = (self.runningOperations.count > 0);
    }
    return isRunning;
}
//线程安全的移除下载operation
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
        if (operation) {
            [self.runningOperations removeObject:operation];
        }
    }
}

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  error:(nullable NSError *)error
                                    url:(nullable NSURL *)url {
    [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}

- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
                             completion:(nullable SDInternalCompletionBlock)completionBlock
                                  image:(nullable UIImage *)image
                                   data:(nullable NSData *)data
                                  error:(nullable NSError *)error
                              cacheType:(SDImageCacheType)cacheType
                               finished:(BOOL)finished
                                    url:(nullable NSURL *)url {
    dispatch_main_async_safe(^{
        if (operation && !operation.isCancelled && completionBlock) {
            completionBlock(image, data, error, cacheType, finished, url);
        }
    });
}

@end


@implementation SDWebImageCombinedOperation
//set方法
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}
//SDWebImageOperation协议方法
- (void)cancel {
    @synchronized(self) {
        self.cancelled = YES;
        if (self.cacheOperation) {
            [self.cacheOperation cancel];
            self.cacheOperation = nil;
        }
        if (self.cancelBlock) {
            self.cancelBlock();
            self.cancelBlock = nil;
        }
    }
}

@end

上面是SDWebImageManager的重点节点流程图:

GitHub

PODS:
- SDWebImage (4.2.3):
- SDWebImage/Core (= 4.2.3)
- SDWebImage/Core (4.2.3)

DEPENDENCIES:
- SDWebImage

SPEC CHECKSUMS:
SDWebImage: 791bb72962b3492327ddcac4b1880bd1b5458431

PODFILE CHECKSUM: 7fbc0b76fb4d0b0b2afa7d3a90b7bd68dea25abb

COCOAPODS: 1.3.1

3>SDImageCache

SDImageCache是SDWebImage的缓存中央。分三有的组成memory内存缓存,disk硬盘缓存和无缓存组成。
SDImageCache.h文件注明:

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    无缓存类型
    SDImageCacheTypeNone,
    磁盘缓存
    SDImageCacheTypeDisk,
    内存缓存
    SDImageCacheTypeMemory
};
@interface SDImageCache : NSObject
#pragma mark - Properties
//缓存配置对象,包含所有配置项
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
//设置内存容量大小
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//设置内存缓存最大值limit
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

#pragma mark - Singleton and initialization
//返回SDImageCache单例
+ (nonnull instancetype)sharedImageCache;
//根据特定的namespace返回一个新的缓存对象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
//根据特定的namespace和directory返回一个新的缓存对象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

#pragma mark - Cache paths
//生成缓存路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
//添加一个只读的缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path;

#pragma mark - Store Ops
//根据key去异步缓存image,缓存在内存和磁盘
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,toDisk未NO不存储在磁盘
- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,toDisk未NO不存储在磁盘 多加一个imageData图片data
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根据key去异步缓存image,缓存在磁盘
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

#pragma mark - Query and Retrieve Ops
//异步检查图片是否缓存在磁盘中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//在缓存中查询对应key的数据
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
//在内存缓存中查询对应key的图片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
//在磁盘缓存中查询对应key的图片
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
//在缓存中查询对应key的图片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;

#pragma mark - Remove Ops
//删除缓存中指定key的图片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
//删除缓存中指定key的图片 磁盘是可选项
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;

#pragma mark - Cache clean Ops
//清空所有的内存缓存
- (void)clearMemory;
//异步清除所有的磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
//异步清除所有的失效的缓存图片-因为可以设定缓存时间,超过则失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

#pragma mark - Cache Info
//得到磁盘缓存的大小size
- (NSUInteger)getSize;
//得到在磁盘缓存中图片的数量
- (NSUInteger)getDiskCount;
//异步计算磁盘缓存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;

#pragma mark - Cache Paths
//获取给定key的缓存路径 需要一个根缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
//获取给定key的默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;

@end

SDImageCache.m注解:

// See https://github.com/rs/SDWebImage/pull/1141 for discussion
//自定义内存缓存类
@interface AutoPurgeCache : NSCache
@end

@implementation AutoPurgeCache

- (nonnull instancetype)init {
    self = [super init];
    if (self) {
#if SD_UIKIT
        //添加通知,当受到内存警告则移除所有的缓存对象
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}

- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end

//内联函数获得该图片的缓存大小 注意乘以屏幕的比例
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

@interface SDImageCache ()

#pragma mark - Properties
//内存缓存对象
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
//保存缓存路径的数组
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
//执行处理输入输出的队列
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;

@end


@implementation SDImageCache {
    NSFileManager *_fileManager;
}

#pragma mark - Singleton, init, dealloc
//生成单例SDImageCache
+ (nonnull instancetype)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (instancetype)init {
    return [self initWithNamespace:@"default"];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}
//初始化磁盘缓存路径和内存缓存name
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

        // Create IO serial queue
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

        _config = [[SDImageCacheConfig alloc] init];

        // Init the memory cache
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        // Init the disk cache
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });

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

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

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    SDDispatchQueueRelease(_ioQueue);
}

- (void)checkIfQueueIsIOQueue {
    //dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)获取当前队列的名字
    //dispatch_queue_get_label获取队列的名字,如果队列没有名字,返回NULL
    const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
    const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
    if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
        NSLog(@"This method should be called from the ioQueue");
    }
}

#pragma mark - Cache paths
////添加一个只读的缓存路径
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
    if (!self.customPaths) {
        self.customPaths = [NSMutableArray new];
    }

    if (![self.customPaths containsObject:path]) {
        [self.customPaths addObject:path];
    }
}
//获取给定key的缓存路径 需要一个根缓存路径
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
    NSString *filename = [self cachedFileNameForKey:key];
    return [path stringByAppendingPathComponent:filename];
}
//获取给定key的默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
//根据key值生成文件名:采用MD5
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSURL *keyURL = [NSURL URLWithString:key];
    NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
    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;
}
//生成磁盘路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

#pragma mark - Store Ops

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //缓存到内存
    if (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    //缓存到磁盘,采用异步操作
    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, use PNG format
                    //如果没有data则采用png的格式进行format
                    data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
                }
                [self storeImageDataToDisk:data forKey:key];
            }

            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}
//利用key进行缓存data
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }

    [self checkIfQueueIsIOQueue];
    //如果文件中不存在磁盘缓存路径 则创建
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }

    // get cache Path for image key  得到该key的缓存路径
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl  将缓存路径转化为url
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //将imageData存储起来
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

    // disable iCloud backup  如果调用者关闭icloud 关闭iCloud备份
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

#pragma mark - Query and Retrieve Ops
//异步检查图片是否缓存在磁盘中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
    dispatch_async(_ioQueue, ^{
        BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];

        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        if (!exists) {
            exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
        }

        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(exists);
            });
        }
    });
}
//在内存缓存中查询对应key的图片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memCache objectForKey:key];
}
//在磁盘缓存中查询对应key的图片 并且如果允许内存缓存则在内存中缓存
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }

    return diskImage;
}
//在缓存中查询对应key的图片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        return image;
    }

    // Second check the disk cache...
    image = [self imageFromDiskCacheForKey:key];
    return image;
}
//根据key在磁盘缓存中搜索图片data
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    NSString *defaultPath = [self defaultCachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }

    NSArray<NSString *> *customPaths = [self.customPaths copy];
    for (NSString *path in customPaths) {
        NSString *filePath = [self cachePathForKey:key inPath:path];
        NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }

        // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
        // checking the key with and without the extension
        imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }
    }

    return nil;
}
//根据key在磁盘缓存中搜索图片dimage
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
        //根据图片的scale或图片中的图片组 重新计算返回一张新图片
        image = [self scaledImageForKey:key image:image];
        if (self.config.shouldDecompressImages) {
            image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
        }
        return image;
    } else {
        return nil;
    }
}

- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
    return SDScaledImageForKey(key, image);
}
//在缓存中查询对应key的图片信息 包含image,diskData以及缓存的类型
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache...如果内存缓存包含图片数据则回调结束
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if (image.images) {//imageData都存储在磁盘
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //到这里说明已经来到磁盘缓存区获取 然后回调结束
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

#pragma mark - Remove Ops
//删除缓存中指定key的图片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    [self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
//删除缓存中指定key的图片 磁盘是可选项
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    if (key == nil) {
        return;
    }
    //如果内存也缓存了,则删除内存缓存
    if (self.config.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    if (fromDisk) {
        dispatch_async(self.ioQueue, ^{
            [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }

}

# pragma mark - Mem Cache settings

- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
    self.memCache.totalCostLimit = maxMemoryCost;
}

- (NSUInteger)maxMemoryCost {
    return self.memCache.totalCostLimit;
}

- (NSUInteger)maxMemoryCountLimit {
    return self.memCache.countLimit;
}

- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
    self.memCache.countLimit = maxCountLimit;
}

#pragma mark - Cache clean Ops
//清空所有的内存缓存
- (void)clearMemory {
    [self.memCache removeAllObjects];
}
//异步清除所有的磁盘缓存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
    dispatch_async(self.ioQueue, ^{
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        [_fileManager createDirectoryAtPath:self.diskCachePath
                withIntermediateDirectories:YES
                                 attributes:nil
                                      error:NULL];

        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

- (void)deleteOldFiles {
    [self deleteOldFilesWithCompletionBlock:nil];
}
//异步清除所有失效的缓存图片-因为可以设定缓存时间,超过则失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        //resourceKeys数组包含遍历文件的属性,NSURLIsDirectoryKey判断遍历到的URL所指对象是否是目录,
        //NSURLContentModificationDateKey判断遍历返回的URL所指项目的最后修改时间,NSURLTotalFileAllocatedSizeKey判断URL目录中所分配的空间大小
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        // This enumerator prefetches useful properties for our cache files.
        //利用目录枚举器遍历指定磁盘缓存路径目录下的文件,从而我们活的文件大小,缓存时间等信息
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //计算过期时间,默认1周以前的缓存文件是过期失效
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        //保存遍历的文件url
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        //保存当前缓存的大小
        NSUInteger currentCacheSize = 0;

        // Enumerate all of the files in the cache directory.  This loop has two purposes:
        //
        //  1. Removing files that are older than the expiration date.
        //  2. Storing file attributes for the size-based cleanup pass.
        //保存删除的文件url
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        //遍历目录枚举器,目的1删除过期文件 2纪录文件大小,以便于之后删除使用
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            //获取指定url对应文件的指定三种属性的key和value
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

            // Skip directories and errors.
            //如果是文件夹则返回
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            // Remove files that are older than the expiration date;
            //获取指定url文件对应的修改日期
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            //如果修改日期大于指定日期,则加入要移除的数组里
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }

            // Store a reference to this file and account for its total size.
            //获取指定的url对应的文件的大小,并且把url与对应大小存入一个字典中
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }
        //删除所有最后修改日期大于指定日期的所有文件
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // If our remaining disk cache exceeds a configured maximum size, perform a second
        // size-based cleanup pass.  We delete the oldest files first.
        //如果当前缓存的大小超过了默认大小,则按照日期删除,直到缓存大小<默认大小的一半
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            // Target half of our maximum cache size for this cleanup pass.
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

            // Sort the remaining cache files by their last modification time (oldest first).
            //根据文件创建的时间排序
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                     usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                         return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                                     }];

            // Delete files until we fall below our desired cache size.
            //迭代删除缓存,直到缓存大小是默认缓存大小的一半
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        //在主线程中回调
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

#if SD_UIKIT
//应用进入后台的时候,调用这个方法 然后清除过期图片
- (void)backgroundDeleteOldFiles {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    [self deleteOldFilesWithCompletionBlock:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}
#endif

#pragma mark - Cache Info
//得到磁盘缓存的大小size
- (NSUInteger)getSize {
    __block NSUInteger size = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        for (NSString *fileName in fileEnumerator) {
            NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
            NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
            size += [attrs fileSize];
        }
    });
    return size;
}
//得到在磁盘缓存中图片的数量
- (NSUInteger)getDiskCount {
    __block NSUInteger count = 0;
    dispatch_sync(self.ioQueue, ^{
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
        count = fileEnumerator.allObjects.count;
    });
    return count;
}
//异步计算磁盘缓存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];

    dispatch_async(self.ioQueue, ^{
        NSUInteger fileCount = 0;
        NSUInteger totalSize = 0;

        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:@[NSFileSize]
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];

        for (NSURL *fileURL in fileEnumerator) {
            NSNumber *fileSize;
            [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
            totalSize += fileSize.unsignedIntegerValue;
            fileCount += 1;
        }

        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock(fileCount, totalSize);
            });
        }
    });
}

@end

下图是在SDWebImageManager中调用SDCacheImage查找缓存的要紧流程图:

此篇作品讲解的是UIImageView+WebCache/UIView+WebCache,SDWebImageManager和SDImageCache三个相当主要类,有诸多很值得大家去学习的点,例如:
1.善用接口分离原则-设计更好的对外调用API。
2.适用作极度处理机制-那几个格外处理可以幸免消耗不要求的财富仍然尤其爆发。例如SDWebImageManager中

 if (!url) { return @"";}
 if ([url isKindOfClass:NSString.class]) {
     url = [NSURL URLWithString:(NSString *)url];
 }

3.充实互斥锁-起到线程的保证功用。

@synchronized (self.failedURLs) {
     isFailedUrl = [self.failedURLs containsObject:url];
 }

4.多接纳inline函数-升高程序的执行功效。

FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
    return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
    return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

5.精粹纷呈运用封装思想和支行概念,写出更为平台化的组件-例如大家平常行使SD大多都是运用UIImageView+WebCache的API,
其实SDWebImageManager是截然可以抽出来单独行使,不会因为跳过了UIImageView+WebCache没了借助而不能运用。即使项目是内需两人跨多单位合作,如若客人或任何单位必要调用你写的3个很牛逼的意义,那那几个平台化就很重大了。
SDWebImage还有众多值得我们借鉴和学习的地点,必要我们细细研读。下一篇会讲解SDWebImageDownloader和SDWebImageDownloaderOperation。

行事原理

引用GitHub上3个导图

爱博体育 2

  • 1、外部API入口。
    通过UIImageView+WebCache
    sd_setImageWithURL艺术(等)作为入口来加载图片。
  • 2、内部API汇总。
    通过UIView+WebCache的’sd_internalSetImageWithU酷威L’对UIImageView、UIButton
    、MKAnnotationView中图纸的下载请求举行集中。
  • 三 、起头加载图片。
    通过SDWebImageManagerloadImageWithURL对图片进行加载。
  • 四 、查找本地
    通过SDImageCachequeryCacheOperationForKey摸索缓存中是否存在图片。假设不设有再经过diskImageDataBySearchingAllPathsForKey拓展磁盘搜索。
  • 伍 、再次来到本地图片给SDWebImageManager
  • 陆 、下载图片
    如果当地查询不到相应图片、则经过SDImageDownloaderdownloadImage开展图片下载。
  • ⑦ 、下载落成再次回到图片给SDWebImageManager
  • 8、由UIView+WebCache通过storeImage将下载图片保存本地
  • ⑨ 、重临图片给UIView+WebCache
  • ⑩ 、设置图片
    其中。

事情层级

  • 总体架构不难分为三层。

最上层:

承担作业的过渡、图片的插入

#import "UIImageView+WebCache.h"
#import "UIButton+WebCache.h"
#import "UIImageView+HighlightedWebCache.h"
//以及其汇总的
#import "UIView+WebCache.h"

逻辑层

顶住不同门类业务的分发。
读取(或写入)缓存(或磁盘)、下载等切实逻辑处理。

#import "SDWebImageManager.h"

业务层

肩负具体事务的兑现

//缓存&&磁盘操作
#import "SDImageCache.h"
//下载操作
#import "SDWebImageDownloader.h"

理所当然、还有任何的工具类。但主要的、就是上面几个。


主导代码(寻常读取下载图片)

  • #### 最上层:UIView+WebCache

有着的代码最后都会集中到

#import "UIView+WebCache.h"
/**
 * @param url            图片地址链接
 * @param placeholder    占位图
 * @param options        下载图片的枚举。包括优先级、是否写入硬盘等
 * @param operationKey   一个记录当前对象正在加载操作的key、保证只有最新的操作在进行、默认为类名。
                         所以如果你想下载多个图片并且都展示一下、可以尝试自定义几个operationKey来操作。(我猜)
 * @param setImageBlock  给开发者自定义set图片的callback
 * @param progressBlock  下载进度callback
 * @param completedBlock 下载完成的callback(sd已经给你set好了、只是会把图片给你罢了)
 * @param context        一些额外的上下文字典。比如你可以搞一个专属的imageManager进来干活。
 */
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary *)context {
    //以当前实例的class作为OperationKey
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //清除当前OperationKey下正在进行的操作。节省无用功
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    //给对象实例绑定imageURLKey = url
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //是否先加载占位图
    if (!(options & SDWebImageDelayPlaceholder)) {
        if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
            dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
            dispatch_group_enter(group);
        }
        //到主线城更新UI
        dispatch_main_async_safe(^{
            //set 占位图
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

    if (url) {
        // 小菊花
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }

        // 允许开发者指定一个manager来进行操作
        SDWebImageManager *manager;
        if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
            manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
        } else {
            manager = [SDWebImageManager sharedManager];
        }

        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            //图片下载||读取完成
            __strong __typeof (wself) sself = wself;
            //小菊花
            [sself sd_removeActivityIndicator];
            if (!sself) { return; }
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否不插入图片
            //1、有图片、但是主动配置
            //2、没图片、设置了延迟加载占位图
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                //
                if (!sself) { return; }
                if (!shouldNotSetImage) {
                    [sself sd_setNeedsLayout];
                }
                if (completedBlock && shouldCallCompletedBlock) {
                    //操作完成的回调
                    completedBlock(image, error, cacheType, url);
                }
            };

            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {
                //如果不显示图片、直接回调。
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }

            /**自动插入图片***/

            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }

            if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
                dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
                dispatch_group_enter(group);
                dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                });
                // ensure completion block is called after custom setImage process finish
                dispatch_group_notify(group, dispatch_get_main_queue(), ^{
                    callCompletedBlockClojure();
                });
            } else {
                dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    callCompletedBlockClojure();
                });
            }
        }];

        //在读取图片之前。向正在进行加载的HashMap中加入当前operation
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

壹个简短的流程图

爱博体育 3

UIView+WebCache流程图

  • #### 逻辑层:SDWebImageManager

SDWebImage中最大旨的类、调度那图片的下载(SDWebImageDownloader)以及缓存(SDImageCache)。

此外、SDWebImageManager并不依托于UIView+WebCache、完全能够单独行使。
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {

    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    //所以、我们并不需要在外部把字符串变为NSURL。
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    //下载操作的对象
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            //线程安全
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }

    //url为空 || (未设置失败重试 && 这个url已经失败过)
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        //发出一个获取失败的回调
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }

    //将操作添加到正在进行的操作数池
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    //默认就是url作为key、也可以自定义mananger的相关block
    NSString *key = [self cacheKeyForURL:url];
    //通过key、查找本地图片
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            //操作被取消、移除操作池
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }

        //本地没有图片 || 刷新缓存
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //有本地图片。但需要被刷新
            if (cachedImage && options & SDWebImageRefreshCached) {
                //先回调出去本地图片。再继续下载操作
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

            //下面是根据调用者传进来的option,来匹配设置了哪些,就给downloaderOptions赋值哪些option
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;

            if (cachedImage && options & SDWebImageRefreshCached) {
                // force progressive off if image already cached but forced refreshing
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }

            //下载图片
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost
                        && error.code != NSURLErrorNetworkConnectionLost) {
                        @synchronized (self.failedURLs) {
                            //失败记录
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    if ((options & SDWebImageRetryFailed)) {
                        //失败重新下载
                        @synchronized (self.failedURLs) {
                            //从失败记录移除
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否磁盘缓存
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
                        //缩放
                        downloadedImage = [self scaledImageForKey:key image:downloadedImage];
                    }

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {

                        //是否需要转换图片
                        //成功下载图片、自定义实现了图片处理的代理
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //获取转换用户后的图片
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            //用户处理成功
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];

                                //用户处理的后若未生成新的图片、则保存下载的二进制文件。
                                //不然则由imageCache内部生成二进制文件保存
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            //回调
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        //下载成功且未自定义代理--默认保存
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }

                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            @synchronized(operation) {
                operation.cancelBlock = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            }
        } else if (cachedImage) {
            //本地有图片--回调、关闭当前操作
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            //本地没有、也不下载--回调、关闭当前操作
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}

爱博体育 4

SDWebImageManager流程图

  • #### 业务层:

缓存&&磁盘操作(SDImageCache)

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // First check the in-memory cache...
    //搜索磁盘缓存
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if (image.images) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            //搜索硬盘
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            //缓存到内存、默认为YES
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //使用NSChache缓存。
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });
    return operation;
}

//查询缓存
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    //self.memCache  为NSCache实例
    return [self.memCache objectForKey:key];
}

//查询磁盘
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
    if (data) {
        //图片解码、调整方向
        UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
        //调整图片缩放比例 @2x/@3x
        image = [self scaledImageForKey:key image:image];
        //压缩图片
        if (self.config.shouldDecompressImages) {
            image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
        }
        return image;
    } else {
        return nil;
    }
}

//写入缓存 && 磁盘
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    if (self.config.shouldCacheImagesInMemory) {
        //写入缓存
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }

    if (toDisk) {
        //写入磁盘
        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;
                    if (SDCGImageRefContainsAlpha(image.CGImage)) {
                        format = SDImageFormatPNG;
                    } else {
                        format = SDImageFormatJPEG;
                    }
                    data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
                }
                [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 checkIfQueueIsIOQueue];
    //如果文件中不存在磁盘缓存路径 则创建
    if (![_fileManager fileExistsAtPath:_diskCachePath]) {
        [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }

    // get cache Path for image key  得到该key的缓存路径
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl  将缓存路径转化为url
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //将imageData存储起来
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

    // disable iCloud backup  如果调用者关闭icloud 关闭iCloud备份
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

出于那里只归咎平常读取下载流程的代码、所以任何关于图片过期&&释放流程的代码没有列出。后边会挨个开展总结。

爱博体育 5

寻找本地流程图

下载操作(SDWebImageDownloader)

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;

    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        //创建下载operation
        __strong __typeof (wself) sself = wself;
        //超时时间
        NSTimeInterval timeoutInterval = sself.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 缓存协议
        //默认NSURLRequestReloadIgnoringLocalCacheData从原地址重新下载
        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;

        //创建下载请求
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];

        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            //默认 image/*;q=0.8
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }

        //创建下载操作
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        //是否解压
        operation.shouldDecompressImages = sself.shouldDecompressImages;

        //证书
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            //默认 账号密码为空的通用证书
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }

        //优先级。默认都不是
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        //向下载队列 NSOperationQueue 中 添加本次下载操作
        [sself.downloadQueue addOperation:operation];

        //设置下载的顺序 是按照队列还是栈
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency

            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

//通过progressBlock&&completedBlock以及Url和SDWebImageDownloaderOperation对token进行包装
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                   forURL:(nullable NSURL *)url
                                           createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
    // 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 != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    dispatch_barrier_sync(self.barrierQueue, ^{
        SDWebImageDownloaderOperation *operation = self.URLOperations[url];
        if (!operation) {
            operation = createCallback();
            //将url作为key、对应的下载操作operation作为value保存。
            self.URLOperations[url] = operation;

            __weak SDWebImageDownloaderOperation *woperation = operation;
            operation.completionBlock = ^{
                dispatch_barrier_sync(self.barrierQueue, ^{
                    SDWebImageDownloaderOperation *soperation = woperation;
                    if (!soperation) return;
                    if (self.URLOperations[url] == soperation) {
                        //下载完成、移除操作
                        [self.URLOperations removeObjectForKey:url];
                    };
                });
            };
        }

        //将成progressBlock以及completedBlock组装成SDCallbacksDictionary.
        id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

        //生成下载任务标识。用于manager将来定位对应操作用
        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}
SDWebImageDownloaderOperation是切实可行下载操作、设计很多互连网层的东西。以后可以独立开一篇、结合AFNetWorking没准会更好。

一部分启迪

  • ##### 分层的接口API设计。

#import "UIImageView+WebCache.h"
#import "UIButton+WebCache.h"
#import "UIImageView+HighlightedWebCache.h"
//以及其汇总的
#import "UIView+WebCache.h"

具有外层API与现实事务毫不相关。
使得SDWebImageManager可以退出View层单独运维。

  • ##### 线程安全

@synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
if (url) {
     @synchronized (self.failedURLs) {
         isFailedUrl = [self.failedURLs containsObject:url];
     }
}
.....

有着大概滋生财富掠夺的目的操作、全体有标准锁珍爱。
不过出于内嵌卓殊处理代码的存在、条件锁的质量是怀有锁中最差的。不了解干什么SD中利用那样多。

  • ##### 内联函数

更高效的短函数执行、替代表明式形式的宏定义。

  • ##### 精细的缓存管理标准化

详参上文提到的《磁盘清理的标准?》

  • ##### 回调设计

SDWebImage中采用了三种、Block以及Delegate。

  • Block使用的很多、举三个例子。

======>#import "UIView+WebCache.h"
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary *)context;

======>SDWebImageDownloader
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

再来看代理

@protocol SDWebImageManagerDelegate <NSObject>

@optional

/**
 * Controls which image should be downloaded when the image is not found in the cache.
 *
 * @param imageManager The current `SDWebImageManager`
 * @param imageURL     The url of the image to be downloaded
 *
 * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
 */
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

简单看出、SDWebImage对回调的采用倾向于:

  • Block
    单个图片的分类、单个图片的下载。
    各样操作职务中必现的progress以及completed。
    所以、有很强的私有绑定须要依旧使用次数不多时、倾向使用block
  • Delegate
    SDWebImageManager下载完结将来的自定义图片处理、是还是不是下载有些url。
    那多个法子如若须要的话都是将会调用数十回的。所以、用Delegate更好、可以将艺术常驻。
  • 同理
    UITableView的利用Delegate、是用为在滚动途中、代理方法须要被不断的进行。
    UIButton也是将会被一再点击。
    UIView的卡通/GCD则足以应用Block、因为只举办一回、用完释放。
    据此、在一般行使中、大家也得以参考上述原则进行设计。
  • ##### NSMapTable

用NSMapTable代替字典来储存当前正在开展的操作、并且将value设置为NSMapTableWeakMemory。防止对应value因为强引用无法自动释放。


权且想到的就那些、更加多难题欢迎留言。