SDWebImage 源码阅读之缓存策略

[imageView sd_setImageWithURL: url placeholderImage:nil options:SDWebImageRefreshCached];

在这样一行代码下,SDWebImage究竟替我们做了什么呢?

SDWebImage的缓存策略

总体来说,SDWebImage会先从内存缓存里查有没有这张图;若没有,去沙盒缓存中查看;若还是没有,显示占位图片,判断有没有这个下载操作的operation,若没有,再创建operation,并放在全局队列中异步执行下载操作,图片下载好之后,将图片放在内存缓存中,并放在沙盒缓存中,再执行回调。


SDWebImage 源码阅读之缓存策略_第1张图片
SDWebImage实现.png

进行异步加载的操作主要是SDWebImageManager,先查看是否有内存缓存,再查看是否有磁盘缓存,在两者都没有的情况下,开启下载线程,下载网络图片。

另外,掌握了 SDWebImageOptions 有哪些选项,便于我们了解 SDWebImage 为我们提供了哪些功能,如下:

SDWebImageOptions 含义
SDWebImageRetryFailed 默认情况下,若一个url下载失败了,这个url就会被加入黑名单,下次不再尝试下载。设置这个选项即使失败了还尝试下载
SDWebImageLowPriority 较低的优先级,默认情况下图片的下载将发生在主线程中,这个选项使下载的优先级较低,推迟下载
SDWebImageHighPriority 较高的优先级,可以让这张图片的加载位于加载队列的前方
SDWebImageCacheMemoryOnly 将关掉磁盘缓存
SDWebImageProgressiveDownload 以渐进的形式展现图片
SDWebImageRefreshCached 帮助处理url相同,但图片资源修改的情况,需要在请求头中加入If-Modified-Since首部,若返回304表示资源未修改,可使用缓存中数据,若修改了,需要重新下载已获得修改后的资源
SDWebImageContinueInBackground app进入后台,图片的下载操作仍可进行,但是,若app被挂起了,下载操作就停止了
SDWebImageHandleCookies 处理Cookies
SDWebImageAllowInvalidSSLCertificates 允许不被信任的SSL证书,测试开发时可临时使用,上架时记得打开
SDWebImageDelayPlaceholder 在image已经加载完了才加载占位图片
SDWebImageTransformAnimatedImage 图片形变
SDWebImageAvoidAutoSetImage 当图片下载好了,手动的加载

可异步加载图片的主要有两个分类,UIImageView+WebCache 和 UIButton+WebCache,只是UIButton多了一个根据不同state来设置背景图片的操作,在显示图片时,调用SDWebImageManager的downloadImageWithURL方法,下面我们先看这两个分类:

//UIImageView+WebCache.m  
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock
{
    //当设置异步加载图片时,先从队列中去掉self的加载操作,保证只做一次操作
    [self sd_cancelCurrentImageLoad];
    //为self添加imageURL
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //若options不是直到图片加载完成才加载placeholder,此时立即将图片设置为placeholder
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }
    
    //url存在时
    if (url) {

        // check if activityView is enabled or not
        // 添加转动图
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        //避免循环引用,声明一个指向self的weak引用
        __weak __typeof(self)wself = self;
        //用SDWebImageManager加载图片
        id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
          
            //下载完图片的回调
            [wself removeActivityIndicator];
            //若此时self已经释放了,返回
            if (!wself) return;
            //保证block在主线程中进行
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    //若取得image,并且options为图片加载完成后,手动替换,则执行回调
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    //若取得image,并且options为马上自动替换placeholder,立即替换
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    //未取得image,并且options为直到图片加载完成,才加载placeholder,此时将图片设置为                       placeholder
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //在操作缓存字典里添加operation,表示当前操作正在进行
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        //url不存在时,在执行完的回调中,传入error为空
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

//用一个字典operationDictionary存储操作的缓存
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
    [self sd_cancelImageLoadOperationWithKey:key];
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    [operationDictionary setObject:operation forKey:key];
}

- (NSMutableDictionary *)operationDictionary {
    //运用OC运行时,动态的为self关联属性
    NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}      
//UIButton+WebCache.m
//UIButton中操作类似,但多了一个根据不同state来设置背景图片的操作
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock
//SDWebImageManager.h
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;

//SDWebImageManager.m
//为了封装性,在.h文件中将其声明为只读,在.m文件中将其声明为可读写
@property (strong, nonatomic, readwrite) SDImageCache *imageCache;//图片缓存管理 
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;//图片下载器


@property (strong, nonatomic) NSMutableSet *failedURLs;//失败的url
@property (strong, nonatomic) NSMutableArray *runningOperations;//记录当前正在进行的操作

- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    //若传来的url为字符串,将其转换为NSURL                                     
    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;
    //加锁访问失败的url集合,防止数据错乱
    @synchronized (self.failedURLs) {
        //判断当前要处理的url是不是曾下载失败的url,防止重复下载
        isFailedUrl = [self.failedURLs containsObject:url];
    }
    
    //当前url为空,且url是曾经下载失败的url,且options不是下载失败重新下载的
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        //在执行完的回调中,传入error为空
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }
    
    //将当前操作加入当前操作集中 
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
   
    //************查询是否存在缓存的图片*************
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        //若操作取消了,及时cancel掉
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }

            return;
        }
    
        //若没有缓存图片||options为有缓存图片也要更新缓存图片&&未响应无缓存则去下载||响应无缓存则去下载
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //有缓存图片&&及时有缓存也要更新缓存图片
            if (image && options & SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                    //下载图片
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }

            // 下载图片
            //设置下载选项为options对应的下载选项
            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 (image && 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;
            }
            //********没有缓存图片,进行下载操作********
            id  subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                    //若任务被取消,则什么也不做
                }
                else if (error) {
                    //若有错误,回传error
                    dispatch_main_sync_safe(^{
                        if (strongOperation && !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                    });
                    
                    //将当前url添加至错误url名单中
                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //下载成功
                    //若需要下载失败后重新下载,将当前url从错误url名单中移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    //options为即使缓存存在,也要刷新图片&&缓存图片存在&&下载后图片不存在
                    if (options & SDWebImageRefreshCached && image && !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];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }

                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }
                    else {
                        //图片下载成功并结束
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }

                        dispatch_main_sync_safe(^{
                            if (strongOperation && !strongOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }
                //若完成,从当前操作组中移除当前操作
                if (finished) {
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }
            }];
            operation.cancelBlock = ^{
                [subOperation cancel];
                
                @synchronized (self.runningOperations) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
            };
        }
        //*******有缓存图片,直接回调*******
        else if (image) {
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !strongOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
        else {
            //没有缓存图片,且下载被代理终止了,回调
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !weakOperation.isCancelled) {
                    completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                }
            });
            //删掉当前操作
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
    }];

    return operation;
}     

在 SDWebImageManager 中的处理解决了很多问题,比如 UITableView 中图片易出现错乱,造成原因为在 cellForRow 里设置 cell 图片的数据源,若 cell 的 imageView 开启下载任务了,然后这个cell被重用,新的 image 数据源会开启新的下载任务,下载任务做完,两个imageView会回调给同一个cell,出现错误。
在滚动到新的一个 cell 时,及时查看当前 cell 的 image 加载有没有被取消,避免耗时加载了一张再也不会被出现的图片。

SDWebImageManager 用到了两个类,SDImageCache 和 SDWebImageDownloader 分别用于查看当前图片的缓存与下载网络图片。

//SDImageCache.m
@property (strong, nonatomic) NSCache *memCache;//内存缓存
//NSCache类是系统提供的一种类似于集合(NSMutableDictionary)的缓存。具有自动删除的功能,以减少系统占用的内存;是线程安全的,不需要加线程锁;键对象不会像 NSMutableDictionary 中那样被复制。
@property (strong, nonatomic) NSString *diskCachePath;//磁盘缓存路径
@property (strong, nonatomic) NSMutableArray *customPaths;//自定义路径
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;//ioQueue唯一子线程

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
    if (!doneBlock) {
        return nil;
    }

    if (!key) {
        doneBlock(nil, SDImageCacheTypeNone);
        return nil;
    }

    //*****************先查看内存缓存*********************
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }
    
    //************若内存中不存在,查看磁盘缓存**************
    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }

        @autoreleasepool {
            UIImage *diskImage = [self diskImageForKey:key];
            //在磁盘缓存中找到这个值,并且当前需要将磁盘缓存的值移入内存中
            if (diskImage && self.shouldCacheImagesInMemory) {
                //用cost计算缓存所有对象的代价,当内存受限或者缓存所有对象的代价超过了最大允许值,缓存会移除其中部分值
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

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

    return operation;
}
//SDWebImageDownloader.m
@property (strong, nonatomic) NSOperationQueue *downloadQueue;//下载队列
@property (weak, nonatomic) NSOperation *lastAddedOperation;//最后添加的下载操作
@property (assign, nonatomic) Class operationClass;//操作类
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;//回调字典
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;//HTTP请求头
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue 使下载操作串行化
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;


- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;

    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
        NSTimeInterval timeoutInterval = wself.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
        //创建网络请求
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
        operation = [[wself.operationClass alloc] initWithRequest:request
                                                        inSession:self.session
                                                          options:options
                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                                             SDWebImageDownloader *sself = wself;
                                                             if (!sself) return;
                                                             __block NSArray *callbacksForURL;
                                                             dispatch_sync(sself.barrierQueue, ^{
                                                                 callbacksForURL = [sself.URLCallbacks[url] copy];
                                                             });
                                                             for (NSDictionary *callbacks in callbacksForURL) {
                                                                 dispatch_async(dispatch_get_main_queue(), ^{
                                                                     SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                                                                     if (callback) callback(receivedSize, expectedSize);
                                                                 });
                                                             }
                                                         }
                                                        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                                                            SDWebImageDownloader *sself = wself;
                                                            if (!sself) return;
                                                            __block NSArray *callbacksForURL;
                                                            dispatch_barrier_sync(sself.barrierQueue, ^{
                                                                callbacksForURL = [sself.URLCallbacks[url] copy];
                                                                if (finished) {
                                                                    [sself.URLCallbacks removeObjectForKey:url];
                                                                }
                                                            });
                                                            for (NSDictionary *callbacks in callbacksForURL) {
                                                                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                                                                if (callback) callback(image, data, error, finished);
                                                            }
                                                        }
                                                        cancelled:^{
                                                            SDWebImageDownloader *sself = wself;
                                                            if (!sself) return;
                                                            dispatch_barrier_async(sself.barrierQueue, ^{
                                                                [sself.URLCallbacks removeObjectForKey:url];
                                                            });
                                                        }];
        operation.shouldDecompressImages = wself.shouldDecompressImages;
        
        //url证书
        if (wself.urlCredential) {
            operation.credential = wself.urlCredential;
        } else if (wself.username && wself.password) {
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
        }
        //优先级
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        //在下载队列里添加下载操作
        [wself.downloadQueue addOperation:operation];
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [wself.lastAddedOperation addDependency:operation];
            wself.lastAddedOperation = operation;
        }
    }];

    return operation;
}

//用来保存progressBlock和completedBlock
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)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;
    }

    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }

        // Handle single download of simultaneous download request for the same URL
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;

        if (first) {
            createCallback();
        }
    });
}

你可能感兴趣的:(SDWebImage 源码阅读之缓存策略)