美文网首页
SDWebImage4.0源码阅读笔记(二)

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

作者: Bugfix | 来源:发表于2017-07-16 00:14 被阅读196次

    紧接着上一篇文章,在这篇文章里面,我会先从 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涉及到的一些东西

    相关文章

      网友评论

          本文标题:SDWebImage4.0源码阅读笔记(二)

          本文链接:https://www.haomeiwen.com/subject/pgzzhxtx.html