manbetx官方网站

搬迁好小板凳看SDWebImage源码解析(一)SDWebImage4.0+源码参阅(附面试题/流程图)

九月 30th, 2018  |  manbetx官方网站

看了产离开上次勾勒简书博客的时间,已经仙逝了八只多月份了,很羞愧。正好最近种不忙,抽点时间研究下第三方库,朋友建议总结写成博客就如此开篇了。内容篇幅会较丰富,所以要各位看官搬好小板凳看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之二进制所有位为左移动1位,空缺的用0补一起,那么0b00000001变成0b00000010,十进制为2虽然SDWebImageLowPriority值为2。

张冠李戴移1位示意图

逐类推:
SDWebImageCacheMemoryOnly向左移动2位等于4,
SDWebImageProgressiveDownload向左移动3各等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
请求的发起方是孰,这时候发起方应提供科学的用户称和密码(即征信息)。这时候就用NSURLCredential身份验证,更加切实可以翻旋即篇博客。

目录

  • 广泛疑难(面试大全?)
    • 磁盘目录在乌?
    • 极老并发数、超时时长?
    • 图表如何命名?
    • 什么辨别图片类型?
    • 所查看找到的图形的源?
    • 具有下载的图片都以吃形容副缓存?磁盘呢?何时缓存的?
    • 磁盘缓存的时长?清理操作的日点?
    • 磁盘清理的规范?
    • 下载图片时、会用缓存协议么?
    • 下载图片的URL必须是NSURL么?
    • 读取缓存以及读取磁盘的上什么确保线程安全?
  • 系知识点
    • 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_INITIALIZER宏来实现指定构造器,通常是怀念报调用者要就此是措施去初始化类对象,便于规范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];

呢就算是当次退出及后台、或者让杀死的当儿。
此间、还有另外一个沾。
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再多分配一些时空来处理部分耗时任务。

  • #### 磁盘清理的法?

第一、通过时展开清理。(最后修改时>一到)
然后、根据占据内存大小进行清理。(如果占据内存大于上限、则按照日排序、删除到上限的1/2。)
此间我并不曾见到运频率优先级判断、所以当是从未。

- (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、否则每次都见面于原本地方重新下载、而无是用网络协议的缓存逻辑。

  • #### 下充斥图片的URL必须是NSURL么?

不是、在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,否则为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_internalSetImageWithURL来促成,下面具体看下sd_internalSetImageWithURL的落实。具体的源码意思都召开了诠释:

- (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);
            }
        });
    }
}

脚是当就同一历程的简易流程图帮助了解:

准备工作

顺手下充斥了一个新型的 (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没了依靠而一筹莫展采取。如果项目是用差不多总人口过多机构合作,如果别人或者外机构要调用你写的一个特别牛逼的机能,那这平台化就非常重要了。
SDWebImage还有多值得咱们借鉴与读书之地方,需要大家细细研读。下一篇会讲解SDWebImageDownloader和SDWebImageDownloaderOperation。

工作规律

引用GitHub上一个导图

图片 2

  • 1、外部API入口。
    通过UIImageView+WebCache
    sd_setImageWithURL计(等)作为入口来加载图片。
  • 2、内部API汇总。
    通过UIView+WebCache的’sd_internalSetImageWithURL’对UIImageView、UIButton
    、MKAnnotationView中图纸的下载请求进行汇总。
  • 3、开始加载图片。
    通过SDWebImageManagerloadImageWithURL本着图纸展开加载。
  • 4、查找本地
    通过SDImageCachequeryCacheOperationForKey摸缓存中是否存在图片。如果无在更经diskImageDataBySearchingAllPathsForKey开展磁盘搜索。
  • 5、返回本地图片被SDWebImageManager
  • 6、下充斥图片
    比方当地查询不至相应图片、则经过SDImageDownloaderdownloadImage进展图片下载。
  • 7、下载了返回图片被SDWebImageManager
  • 8、由UIView+WebCache通过storeImage用生充斥图片保存本地
  • 9、返回图片于UIView+WebCache
  • 10、设置图片
    其中。

工作层级

  • 全部架构简单分为三重合。

最上层:

荷作业的连片、图片的插入

#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因为强引用不能自动释放。


临时想到的尽管这些、更多问题欢迎留言。

相关文章

标签:, , ,

Your Comments

近期评论

    功能


    网站地图xml地图