SDWebImage相信大家都很熟悉,毕竟大部分项目都会用。
SDWebImage特征
- 提供了UIImageView,UIButton,MKAnnotationView的category来加载网络图片并且对网络图片进行缓存管理
- 异步下载图片
- 异步内存+磁盘缓存,自动缓存过期处理
- 背景图压缩
- 保证同一个URL不会重复几次
- 保证失效的URL不会一次又一次地重试
- 保证主线程不会被阻塞
- 使用GCD和ARC
SDWebImage结构
SDWebImage使用
#import "UIImageView+WebCache.h"
/*
不使用占位图
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"]];
/*
使用占位图
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
/*
使用占位图,并且可以自己选择SDWebImageOptions:
//失败重试
SDWebImageRetryFailed = 1 << 0,
//默认情况下,图片会在交互发生的时候下载(例如你滑动tableview的时候),这个会禁止这个特性,导致的结果就是在scrollview减速的时候才会开始下载
SDWebImageLowPriority = 1 << 1,
//仅支持内存缓存
SDWebImageCacheMemoryOnly = 1 << 2,
//边下载边显示
SDWebImageProgressiveDownload = 1 << 3,
//刷新缓存(更换用户头像)
SDWebImageRefreshCached = 1 << 4,
//启动后台下载
SDWebImageContinueInBackground = 1 << 5,
//通过设置处理存储在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
//允许不安全的SSL证书
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//默认情况下,image在加载的时候是按照他们在队列中的顺序加载的,这个会把他们移动到队列的前端,并且是立刻加载
SDWebImageHighPriority = 1 << 8,
//默认情况下,占位图会在图片下载的时候显示.这个开启会延迟占位图显示的时间,等到图片下载完成之后才显示占位图
SDWebImageDelayPlaceholder = 1 << 9,
//我们通常不会在动画图像上调用transformDownloadedImage委托方法,因为大多数转换代码会对它进行转换,使用这个来转换它们
SDWebImageTransformAnimatedImage = 1 << 10,
//下载完成后手动设置图片,默认是下载完成后自动设置
SDWebImageAvoidAutoSetImage = 1 << 11,
//默认情况下,图像将根据其原始大小进行解码。 在iOS上,此标志会将图片缩小到与设备的受限内存兼容的大小。如果设置该标志,则会关闭缩小比例
SDWebImageScaleDownLargeImages = 1 << 12
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] options:SDWebImageRefreshCached];
/**
可看到下载结果,block返回四个参数
@param image 下载后的图片
@param error 错误
@param cacheType 缓存类型
@param imageURL 下载图片URL
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
/*
SDImageCacheType类型:
//没有缓存,直接下载
SDImageCacheTypeNone,
//从磁盘缓存中获得
SDImageCacheTypeDisk,
//从内存缓存中获得
SDImageCacheTypeMemory
*/
}];
这就是基本的使用方法了,很简单,顺便看下内存缓存和磁盘缓存区别:
- 内存是指当前程序的运行空间,可用来临时存储文件,磁盘是指当前程序的存储空间,可用来永久存储文件。
- 内存缓存速度快容量小,磁盘缓存容量大速度慢可持久化。
iOS采用沙盒机制,每个应用程序只能在该程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被称为沙盒。默认情况下,每个沙盒包含3个文件夹:Documents, Library 和tmp。
- Documents:建议将所有的应用程序数据文件写入到这个目录下。
- Library:存储程序的默认设置或其它状态信息
Library/Caches:存放缓存文件
Library/Preferences: 存放的是UserDefaults存储的信息 - tmp:创建临时文件的地方
SDWebImage剖析
看下SDWebImageSequenceDiagram图:
然后看下
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 {
//validOperationKey
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//根据validOperationKey取消相关的下载任务
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/*
关联对象
源对象:self 关键字:imageURLKey 关联的对象:url 一个关联策略:OBJC_ASSOCIATION_RETAIN_NONATOMIC
关联策略
OBJC_ASSOCIATION_ASSIGN = 0, <指定一个弱引用关联的对象>
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,<指定一个强引用关联的对象>
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, <指定相关的对象复制>
OBJC_ASSOCIATION_RETAIN = 01401, <指定强参考>
OBJC_ASSOCIATION_COPY = 01403 <指定相关的对象复制>
*/
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
//options存在并且options不是SDWebImageDelayPlaceholder,主线程设置占位图
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
//url存在
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
//是否显示进度条
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
//执行下载任务
id 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];
//self是否被释放
if (!sself) { return; }
//图片下载完成或者options为SDWebImageAvoidAutoSetImage block回调结果
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否手动设置图片
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
//self是否被释放
if (!sself) { return; }
//自动设置
if (!shouldNotSetImage) {
//标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定会被调用
[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;
}
BOOL shouldUseGlobalQueue = NO;
//是否有全局队列(global_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, ^{
//设置图片
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
});
}];
//
[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);
}
});
}
}
看下sd_cancelImageLoadOperationWithKey
:
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
//key获取operations
id operations = operationDictionary[key];
//operations不为空
if (operations) {
//SDWebImageOperation数组
if ([operations isKindOfClass:[NSArray class]]) {
for (id operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
//SDWebImageOperation协议
[(id) operations cancel];
}
//SDOperationsDictionary移除当前key
[operationDictionary removeObjectForKey:key];
}
}
然后看下SDOperationsDictionary
是个啥:
//定义字典key为string value为id类型()
typedef NSMutableDictionary SDOperationsDictionary;
//懒加载
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//存在返回
if (operations) {
return operations;
}
//不存在创建
operations = [NSMutableDictionary dictionary];
//关联
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
从loadImageWithURL
看看是如何下载图片的:
- (id )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,如果你想预取图像,使用-[SDWebImagePrefetcher prefetchURLs]
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.
//判断url类型
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
//url是否合法
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//判断url是否失败过
BOOL isFailedUrl = NO;
if (url) {
/*
NSMutableSet:失败url的一个集合
@synchronized结构所做的事情跟锁(lock)类似,它防止不同的线程同时执行同一段代码。
*/
@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;
}
//NSMutableArray
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//根据url生成key
NSString *key = [self cacheKeyForURL:url];
//读取缓存
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//operation取消
if (operation.isCancelled) {
//下载任务移除
[self safelyRemoveOperationFromRunning:operation];
return;
}
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//图片有缓存,options设置为SDWebImageRefreshCached 刷新缓存
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
//下载图片
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;
//operation不存在或者取消
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 {
//设置了失败重试
if ((options & SDWebImageRetryFailed)) {
//将失败的url从failedURLs集合中移除
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否磁盘缓存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//options为刷新缓存,但是没有下载的图片
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];
}
}
if (finished) {
//移除operation
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//移除operation
@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) {
__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;
}
- (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 *{
__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
//是否缓存
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
//是否存储Cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
//header
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
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;
}
//addOperation
[sself.downloadQueue addOperation:operation];
/*
//以队列的方式,按照先进先出的顺序下载。这是默认的下载顺序
SDWebImageDownloaderFIFOExecutionOrder,
//以栈的方式,按照后进先出的顺序下载。
SDWebImageDownloaderLIFOExecutionOrder
*/
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;
}];
}
缓存图片storeImage
:
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
//image key缺一不可
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
//NSCache缓存(内存缓存)
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 = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
}
//磁盘缓存
[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
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
//禁用iCloud备份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
如何从缓存中读取图片queryCacheOperationForKey
:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
//key必须不为空
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
//首先从内存中读取
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
//磁盘读取图片data
NSData *diskData = nil;
if (image.images) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
//回调(SDImageCacheTypeMemory)
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];
}
//回调(SDImageCacheTypeDisk)
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
到这里,基本就差不多了,SDWebImage缓冲用得是NSCatch和NSFileManager:
- NSCache:是系统提供的一种类似于
(NSMutableDictionary)的缓存,它和NSMutableDictionary的不同:- NSCache具有自动删除的功能,以减少系统占用的内存;
- NSCache是线程安全的,不需要加线程锁;
- 键对象不会像 NSMutableDictionary 中那样被复制(键不需要实现 NSCopying 协议)。
- NSFileManager:是iOS中的文件管理类。
有兴趣的可以自己再看看这方面的文档。