SDWebImage4.0源码阅读笔记(二)

字数 1440阅读 230

紧接着上一篇文章,在这篇文章里面,我会先从 SDWebImageManager 中的 loadImageWithURL 这个方法入手来理解在 SDWebImageManager 这一步里面到底做了什么事情。让咱们对整个图片加载流程有个更加详细的理解,随后再分析这个类中其它的一些方法。当然也会顺带把 SDImageCache 的作用详细分析一遍。

SDWebImageManager

SDWebImageManager 同时管理 SDImageCache 和 SDWebImageDownloader两个类,它是这一层的中枢。在加载图片任务开始的时候,SDWebImageManager 首先访问 SDImageCache 来查询是否存在缓存,如果有缓存,直接返回缓存的图片。如果没有缓存,就命令SDWebImageDownloader来下载图片,下载成功后,存入缓存,显示图片。以上是SDWebImageManager大致的工作流程。

先介绍一下这个类中比较重要的属性:

//负责缓存相关的类
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
//负责下载相关的类
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
//记录加载失败的图片URL
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
//记录当前正在进行的任务,可以看到这个任务是 SDWebImageCombinedOperation,下面介绍
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;

1.SDWebImageCombinedOperation
可以看到这个类其实就是实现了上篇文章咱们提到的 SDWebImageOperation 协议,而这个协议也仅仅只有一个cancle方法,在最开始看这里的时候我心里也有疑问,这里 SDWebImageCombinedOperation 也有个cancleBlock,协议也有个cancle方法,这两者到底有什么关系呢???

这里先介绍结论,后面上代码:协议里面的 cancle 方法做的事情是将查询缓存任务和下载任务一并取消掉,而这里面的 cancelBlock 所做的事情其实是去取消下载任务,换句话说,其实就是协议里面的 cancle 方法会调用这个cancleBlock和额外的取消查询缓存。而额外的取消查询缓存的操作也是因为这个 CombinedOperation中包含了查询缓存的cacheOperation,所以起名叫CombinedOperation,包含了下载任务和缓存任务。

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;//取消查缓存和下载操作
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end

2.SDWebImageManagerDelegate

@protocol SDWebImageManagerDelegate <NSObject>

@optional
//决定当缓存中没有找到图片时是否需要下载,返回 NO 的话代表即使在缓存中没有找到图片也不去下载
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;

//允许图片在刚下载回来并且在还没放入缓存之前对图片进行 transform ,返回变换后的图片
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

@end

接下来进入这个非常重要的 loadImageWithURL

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    ...
    //保护措施,如果一不小心传了一个string类型过来,直接将它转为NSURL
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    ...
    //创建一个任务,并同时创建一个weak引用
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    //这个failedURLs其实就是黑名单,判断当前传进来的URL是不是已经在黑名单里面了,后面有用
    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    // url的长度为0 || (下载失败后进入黑名单 && 确实在黑名单中)
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
    // 直接回调完成block
        [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
    NSString *key = [self cacheKeyForURL:url];
    
    //调用 imageCache 去查询缓存,并返回该任务
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {       
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
         //(没有缓存图片) || (即使有缓存图片,也需要更新缓存图片) || (代理没有响应imageManager:shouldDownloadImageForURL:消息,默认返回yes,需要下载图片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下载图片)
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //1. 存在缓存图片 && 即使有缓存图片也要下载更新图片
            if (cachedImage && options & SDWebImageRefreshCached) {             
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            // 2. 如果不存在缓存图片,或者即使有缓存图片也要下载更新图片的情况,直接去下载
          
            .....
          
           //开启下载器下载,subOperationToken 用来标记当前的下载任务,便于被取消            
            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) {
                    // 1. 如果任务被取消,则什么都不做,避免和其他的completedBlock重复
    
                } else if (error) {
                    //2. 如果有错误
                    //2.1 在completedBlock里传入error
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    //2.2 在错误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 {
                     //3.1 下载成功,如果需要下载失败后重新下载,则将当前url从失败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 {
                        //(图片下载成功并结束) 动图
                        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从 表示正在执行的Operation中移除
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            //指定SDWebImageCombinedOperation的取消事件,这里可以看出cancleBlock仅仅是用来cancle下载任务的
            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;
}

至此,这个最复杂的函数,也算是一层一层理清楚了,主要是判断有点多,耐心一点还是没有问题的,接下来我们再把这个类里面的一些别的函数简单看一下 ,然后再进入到SDWebImageCache这个很重要的类中去。

这里面的方法大多数都是对 SDWebImageCache中的方法进行了一层封装,间接的调用 SDWebImageCache 中的方法实现功能

//将图片和他对应的URL存入缓存,url是拿来当key的
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;

//取消正在加载图片的所有任务
- (void)cancelAll;

//判断当前UIImageView或者button是否有正在加载图片的任务
- (BOOL)isRunning;

//根据URL去异步检查它对应的图片是否被缓存了,不论是磁盘还是内存都算,回调总是在主队列
- (void)cachedImageExistsForURL:(nullable NSURL *)url
                     completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

//根据URL区异步仅仅检查图片是否被缓存在了磁盘上
- (void)diskImageExistsForURL:(nullable NSURL *)url
                   completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

//根据URL返回缓存的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

SDImageCache

从 SDWebImageManager 中调用的很重要的这查询缓存入手:
先介绍一下SDImageCacheTypeNone所对应的 SDImageCacheType,可以理解为缓存所在位置:

typedef NS_ENUM(NSInteger, SDImageCacheType) {
   //没有缓存
    SDImageCacheTypeNone,
   //缓存在磁盘
    SDImageCacheTypeDisk,
   //缓存在内存
    SDImageCacheTypeMemory
};
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    //先判断有没有key,如果没有的话,直接走doneBlock
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // 首先先查询内存缓存,内存缓存其实是NSCache,虽然这里封装了一层但其实就是调用objectForKey那个方法
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    if (image) {
        NSData *diskData = nil;
        //如果是gif,就拿到data,后面要传到doneBlock里。不是gif就传nil
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }         
        //因为图片有缓存可供直接使用,所以不用实例化NSOperation来进行耗时的磁盘查询,直接范围nil
        return nil;
    }
  //如果内存中没找到缓存,那肯定只有查磁盘了,接下来进行磁盘缓存查询,实例化NSOperation
    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
//self.ioQueue作者自己创建了一个查询队列,在进行异步查询的时候,因为函数下面的同步return operation 
//操作可能先返回出去,于是就有可能先返回出去的operation被cancle掉,因此作者在这里加了一层判断,看是否被取消掉了,这层考虑非常严谨
        if (operation.isCancelled) {
            // 如果被cancle掉了,那么什么都不用做了
            return;
        }
 //放到autoreleasepool中去查磁盘缓存,降低内存峰值
        @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;
}

磁盘清理缓存逻辑概述:先清除过期的文件;然后判断此时的缓存文件大小是否小于设置的最大大小。若大于最大大小,则进行第二轮的清扫,清扫到缓存文件大小为设置的最大大小的一半。

dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; // 缓存文件的路径
        // 将要获取文件的3个属性(URL是否为目录;内容最后更新日期;文件总的分配大小)
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey,NSURLTotalFileAllocatedSizeKey];
        // 使用目录枚举器获取缓存文件有用的属性
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;
        // 枚举缓存目录的所有文件,此循环有两个目的:
        // 1.清除超过过期日期的文件;
        // 2.为下面根据大小清理磁盘做准备
        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL]; // 传入想获得的该URL路径文件的属性数组,得到这些属性字典。

            // 若该URL是目录,则跳过。
            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }
            // 清除过期文件
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL]; // 把过期的文件url暂时先置于urlsToDelete数组中
                continue;
            }
        // 计算文件总的大小并保存保留下来的文件的引用。
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }
        // 如果剩下的磁盘缓存文件仍然大于我们设置的最大大小,则要执行以大小为基础的第二轮清除
        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
            // 此轮清理的目标是最大缓存的一半
            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
            // 以剩下的文件最后更新时间排序(最老的最先被清除)
            NSArray *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 *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });

介绍完了 SDImageCache 在整个流程中的主要任务之后,咱们再来详细看看SDImageCache这个类的其他的一些东西:

属性

//内存缓存对应的NSCache
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
//自定义的队列,查询磁盘缓存的操作就放在这个队列里
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;
//可以理解为cache的配置信息,下面再讲
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
//支持缓存的最大空间,不过据说设置这个也不是绝对的,详情看NSCache就知道了
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//支持缓存的最大对象数,默认不限制
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
/* ================ SDImageCacheConfig =====================*/
//是否解压下载的图片,拿空间换时间,默认yes
@property (assign, nonatomic) BOOL shouldDecompressImages;
//静止iCloud备份,默认yes
@property (assign, nonatomic) BOOL shouldDisableiCloud;
//允许使用内存来缓存图片,默认yes
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
//缓存的最长时间 默认一周
@property (assign, nonatomic) NSInteger maxCacheAge;
//缓存的最大值,单位byte
@property (assign, nonatomic) NSUInteger maxCacheSize;

方法:

//磁盘缓存路径,默认是在沙盒Cache下面的 com.hackemist.SDWebImageCache文件夹
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
//异步缓存图片到内存与磁盘
- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;        
//异步检查磁盘中是否有缓存        
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//查询缓存,就是Manager中使用的那个
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
....
....
....
其余的很多方法都在源码中都有很详细的注释且理解比较容易,这里就不做过多的介绍,读者可以自行阅读

问题:

1.在缓存图片的时候如何计算图片的占用空间的?

//SDImageCache.m
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
}

很明显在iOS上是用图片的 长 * 宽 * 图片比例 * 图片比例

2.什么时候自动清理缓存呢?(手动调用清理接口的另当别论)

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


很明显可以看到,在初始化 SDImageCache 的时候注册了这么几个通知,所以结论如下:

  • 当收到内存警告时,会清理所有内存缓存

  • 程序终止或者进入后台时,删除磁盘上过期的缓存

2.内存的key是什么,存在磁盘上的文件名是什么?

内存缓存的key直接就是图片的URL,而存磁盘上的缓存文件名是根据URL生成的MD5

3.在存磁盘的时候如何区分图片的文件格式?

==============================< NSData+ImageContentType.h >==================
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    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:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}

很明显可以看到是取服务端返回的数据的第一个字节来做判断

4.哪些事情是放在这个自定义的 ioQueue 上做的?

  • 清理磁盘缓存
  • 计算磁盘缓存的大小的时候
  • 查询磁盘缓存的时候
  • 初始化 NSFileManager 的时候

5.初始化NSFileManager的时候为什么要放在自己的串行队列,然后同步执行呢?为什么要自己去 new 一个Manager实例而不是使用 NSFileManager 的defaultManager呢?

解释一下:同步放在串行队列中的任务其实目的就是为了线程同步,相当于为block中的代码进行加同步锁的操作;在defaultManager文档中也有提到,如果你想使用 NSFileManagerDelegate 来回调一些相关操作通知的话,组好是自己创建和初始化一个 NSFileManager 的实例,可是SD貌似很明显没用到相关delegate;通过多方查找:终于找到一篇文章(此处多谢鹏大神和我一起探究),其实问题就是 NSFileManager 的 defaultManager并不是线程安全的,所以作者才会自己实例化对象,并把它放在同步线程中;

NSFileManager

至于为什么要通过这种方式来进行线程同步,大家其实可以参考 《Effective Objective - C 2.0》中的第41条,说白了其实就是 @synchronized()的效率没有这个高啦。

总结,到这里就把 SDWebImageManager 和 SDImageCache 都大致理了一遍,在下篇文章咱们主要去分析下载模块即SDWebImageDownloader涉及到的一些东西

推荐阅读更多精彩内容