SDWebImage是一个开源的第三方库,它提供了UIImageView的分类来实现从网络端下载数据并缓存到内存和磁盘。
SDWebImage有如下特点:
- 提供了UIImageView和UIButton的分类。以支持加载网络图片并缓存。
- 一个异步的图片下载器
- 提供异步的内存和磁盘缓存,并自动处理缓存过期。
- 后台图片解压缩处理。
- 确保同一个URL不会被下载多次。
- 确保主线程永远不会阻塞。
一.储备知识
SDWebImage中每一个下载任务都是一个SDWebImageDownloaderOperation
,而SDWebImageDownloaderOperation
又是继承自NSOperation
,所以每一个下载任务对应一个NSOperation
。在SDWebImage
中使用SDWebImageDownloader
来管理
多个下载任务,在SDWebImageDownloader
中有一个downloadedQueue
这个属性,这个属性是NSOperationQueue
类型的,也就是用NSOperationQueue
来管理NSOperation
。
下面我们就一起学习一下NSOperation
和NSOperationQueue
。
NSOperation和NSOperationQueue
NSOperation是一个抽象类,用来表示与单个任务相关联的代码和数据。
NSOperation类是一个抽象类,我们不能直接去使用它而是应该创建一个子类来继承它。虽然它只是一个抽象类,但是它的基本实现还是提供了很有价值的逻辑来确保任务的安全执行。这种原生的逻辑可以让我们专注于任务的真正的实现,而不需要用一些胶水代码去确保这个任务能正常工作在其他地方。
我们可以把一个NSOperation对象加入到一个operation queue中,让这个operation queue去决定什么时候执行这个operation。当使用operation queue去管理operation时,轮到某个operation执行时实际是去执行这个operation的start
方法,所以我们一个operation对象实际要执行的任务应该放在start方法里面。如果我们不想使用operation queue,也可以通过手动调用NSOperation的start方法来执行任务。
我们可以通过添加依赖来确定operation queue中operation的具体执行顺序。添加依赖和移除依赖可以使用下列的API:
//添加依赖
- (void)addDependency:(NSOperation *)op;
//移除依赖
- (void)removeDependency:(NSOperation *)op;
只要当一个operation对象的所有依赖都执行完成的时候,其才可以变成熟ready状态,然后才可以被执行。如果一个operation没有添加依赖,直接加入了operation queue中,那么就会按照加入队列的先后顺序,当这个operation的前一个operation执行完成以后,其状态才会变成ready,才能被执行。
NSOperation对象有下列四个比较重要的状态:
- isCancelled
- isExecuting
- isFinished
- isReady
其中isExecuting
,isFinished
,isReady
这三种状态相当于是operation对象的生命周期:
而isCancelled
这种状态则比较特殊,当我们对operation对象调用- (void)cancel
方法时,其isCancelled
属性会被置为YES。这个时候要分两种情况: - operation正在执行
也就是说其状态现在是isExecuting
,调用- (void)cancel
方法后会马上停止执行当前任务,并且状态变为isFinished
,isCancelled = Yes
。 - operation还没开始执行
这个时候operation的状态其实是isReady
这个状态之前,operation还没开始执行,调用- (void)cancel
方法后会去调用operation的start
方法,在start
方法里我们要去处理cancel事件,并设置isFinished = YES
。
SDWebImageOptions
在SDWebImage中大量使用了option类型,通过判断option类型的值来决定下一步应该怎么做,所以如果对这些option值一点都不了解的话可能理解起源码来也会非常难受。SDWebImageOptions
是暴露在外的可供使用者使用的option。还有一些option比如SDImageCacheOptions
, SDWebImageDownloaderOptions
这些都是不暴露给用户使用的。源码中是根据用户设置的SDWebImageOptions
这个option来确定另外两个option的值。
下面我们来具体看一下SDWebImageOptions
这里我们也可以看到,
SDImageCacheOptions
中的三个选项在SDWebImageOptions
中都有对应的选项。
-
SDImageCacheQueryDataWhenInMemory
对应SDWebImageQueryDataWhenInMemory
。 -
SDImageCacheQueryDiskSync
对应SDWebImageQueryDiskSync
。 -
SDImageCacheScaleDownLargeImages
对应SDWebImageScaleDownLargeImages
。
SDWebImageDownloaderOptions
SDWebImageDownloaderOptions
中所有选项在SDWebImageOptions
中也有相对应的选项,这里不再赘述。
SDImageCacheType
//从缓存中得不到数据
SDImageCacheTypeNone,
//从磁盘缓存中得到数据
SDImageCacheTypeDisk,
//从内存缓存中得到数据
SDImageCacheTypeMemory
框架的主要类和一次图片加载的主要流程
框架的主要类
一次图片加载的主要流程
针对上图中一次图片加载的主要流程,每一步做介绍:
- 1.SDWebImage为UIImagView创建了一个分类
UIImageView (WebCache)
,然后UIImageView对象可以调用这个分类的方法来下载图片:
[imageView sd_setImageWithURL:[NSURL URLWithString:@""]];
- 2.
UIImageView (WebCache)
的- (void)sd_setImageWithURL:(nullable NSURL *)url
方法实际调用了UIView (WebCache)
的下列方法:
- (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;
- 3.
UIView (WebCache)
的上述方法在实现时会创建一个SDWebImageManager
的实例对象,然后调用其下列方法来加载图片:
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
- 4.在
SDWebImageManager
对象的上述方法里,首先会查询在缓存中有没有这个图片,然后根据各种option的判断决定是否要从网络端下载。查询缓存中有没有是通过调用SDImageCache
对象的实例方法来实现的:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options
done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- 5.返回缓存查询的结果
- 6.如果需要下载图片,那么调用
SDWebImageDownloader
对象的下列方法进行下载:
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- 7.获取从网络端下载的图片。
- 8.判断是否要将下载的图片进行缓存,如果需要,则缓存。
- 9.把通过
SDWebImageManager
对象获取的图片显示在UIImageView
上。
源码分析
这一部分我们进行详细的源码分析。
首先从SDWebImageManager
类的loadImageWithURL:
方法看起:
由于代码比较长,我就采用注释的方式
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//如果传进来的是一个NSString,则把NSString转化为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;
}
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
//self.failedURLs是nsurl的黑名单,一般情况下,如果URL在这个黑名单里,那么就不会尝试加载这个图片,直接返回
BOOL isFailedUrl = NO;
if (url) {
LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
UNLOCK(self.failedURLsLock);
}
//SDWebImageRetryFailed即即使URL被加入了黑名单,也要尝试加载这个URL对应的图片
//如果URL长度为0,或者URL被加入了黑名单并且没有设置SDWebImageRetryFailed,那么就直接回调完成的block
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;
}
LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock);
NSString *key = [self cacheKeyForURL:url];
//由于我们在使用API的时候只设置SDWebImageOptions,所以这里就是根据用户设置的SDWebImageOptions去设置SDImageCacheOptions
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
__weak SDWebImageCombinedOperation *weakOperation = operation;
//这里开始调用SDImageCache对象的queryCacheOperationForKey:方法去缓存中查找有没有这个URL对应的图片
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
[self safelyRemoveOperationFromRunning:strongOperation];
return;
}
// 判断我们是否需要从网络端下载图片
//首先检查没有设置只能从缓存中获取,然后检查cachedImage = nil或者设置了要刷新缓存,则需要从网络端下载图片
BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
&& (!cachedImage || options & SDWebImageRefreshCached)
&& (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
if (shouldDownload) {
//从缓存中获取了图片并且设置了要刷新缓存这个option,则要进行两次完成的回调,这是第一次回调
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:strongOperation 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
//这里是根据用户设置的SDWebImageOptions来手动设置SDWebImageDownloaderOptions
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) {
//这里其实就是把SDWebImageDownloaderProgressiveDownload这个option去掉
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
//加上SDWebImageDownloaderIgnoreCachedResponse这个option,忽略NSURLCache中缓存的response
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
__weak typeof(strongOperation) weakSubOperation = strongOperation;
//l开始进行图片的下载
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
if (!strongSubOperation || strongSubOperation.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:strongSubOperation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL;
// 后面都是判断在请求失败的情况下是否应该把
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost);
}
if (shouldBlockFailedURL) {
LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
UNLOCK(self.failedURLsLock);
}
}
else {
//如果设置了SDWebImageRetryFailed那么就要把URL从黑名单中移除
if ((options & SDWebImageRetryFailed)) {
LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
UNLOCK(self.failedURLsLock);
}
//判断是否应该把下载的图片缓存到磁盘,SDWebImageCacheMemoryOnly这个option表示只把图片缓存到内存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
// We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.
if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
downloadedImage = [self scaledImageForKey:key image:downloadedImage];
}
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), ^{
@autoreleasepool {
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
NSData *cacheData;
// pass nil if the image was transformed, so we can recalculate the data from the image
if (self.cacheSerializer) {
cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
}
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
});
} else {
//可以直接看到这一部分
if (downloadedImage && finished) {
if (self.cacheSerializer) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
}
});
} else {
//对图片进行缓存
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
}
//第二次调用完成的block
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
[self safelyRemoveOperationFromRunning:strongSubOperation];
}
}];
//如果从从缓存中获取了图片并且不需要下载
} else if (cachedImage) {
//执行完成的回调
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:strongOperation];
} else {
// 缓存中没有获取图片,也不用下载
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
return operation;
}
总结一下SDWebImageManager
的loadImageWithURL:
所做的事情:
其实在
loadImageWithURL:
里面做了加载图片的完整流程。首先检查传入的NSURL的有效性。然后开始从缓存中查找是否有这个图片,得到查询结果之后再根据查询结果和设置的option判断是否需要进行下载操作,如果不需要下载操作那么就直接使用cache image进行下载回调。如果需要进行下载操作那么就开始下载,下载完成后按照设置的option将图片缓存到内存和磁盘,最后进行完成的回调。
然后我们看一下查询缓存的具体过程,也就是SDImageCache
这个类的queryCacheOperationForKey:
方法:
这里也是采用注释的方式
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 首先检查内存缓存中有没有这个图片,注意内存缓存使用的是NSCache,它是一个类字典结构,使用图片对应的nsurl作为key,在查询的时候就用这个key去查询
UIImage *image = [self imageFromMemoryCacheForKey:key];
//是否只查询内存缓存(如果从内存缓存中获取了图片并且没有设置SDImageCacheQueryDataWhenInMemory,那么就只查询内存缓存)
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
//执行回调
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
//从磁盘中查询
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
//j缓存获取的类型,有三种m类型,none,memory,disk
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// 图片是从内存缓存中获取的
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
//图片是从磁盘缓存中获取的
cacheType = SDImageCacheTypeDisk;
// 解压图片
diskImage = [self diskImageForKey:key data:diskData options:options];
//判断是否需要把图片缓存到内存
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//将图片缓存到内存
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
总结一下queryCacheOperationForKey:
方法所做的事情:
SDImageCache
这个类是专门负责缓存相关的问题的,包括查询缓存和将图片进行缓存。SDImageCache
使用了一个NSCache
对象来进行内存缓存,磁盘缓存则是把图片数据存放在应用沙盒的Caches
这个文件夹下。
首先查询内存缓存,内存缓存查询完了以后再判断是否需要查询磁盘缓存。如果查询内存缓存已经有了结果并且没有设置一定要查询磁盘缓存,那么就不查询磁盘缓存,否则就要查询磁盘缓存。内存缓存没有查询到图片,并且磁盘缓存查询到了图片,那么就要把这个内容缓存到内存缓存中。
图片的缓存查询完成后我们再来看一下下载操作,即SDWebImageDownloader
的downloadImageWithURL:
方法
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished) {
//创建一下下载的operation
operation = [self createDownloaderOperationWithUrl:url options:options];
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
//把operation加入到nNSOperationQueue中去
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);
//这一部分代码是在取消operation的时候使用
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
SDWebImageDownloader
这个类是专门管理下载的,它有一个属性是downloadQueue
,这是一个NSOperationQueue
,每创建一个新的下载任务都把它加入到这个downloadQueue
中,让downloadQueue
去管理任务的开始,取消,结束。
上面的方法其实做的事情很简单,就是创建了一个下载图片的operation,然后把它加入到了
downloadQueue
中去。
下面我们来具体看一下创建下载图片的operation的过程,即SDWebImageDownloader
类的createDownloaderOperationWithUrl:
方法:
- (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options {
NSTimeInterval timeoutInterval = self.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;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (self.headersFilter) {
request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = [self allHTTPHeaderFields];
}
//前面都是为了创建一个request,然后使用request和session对象去创建下载的operation
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
operation.shouldDecompressImages = self.shouldDecompressImages;
if (self.urlCredential) {
operation.credential = self.urlCredential;
} else if (self.username && self.password) {
operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
}
//设置operation的队列优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//如果设置的执行顺序是xLIFI,即后进先出,则要把queue中的最后一个加入的operation的依赖设置为该operation,这样来保证这个operation最先执行
if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[self.lastAddedOperation addDependency:operation];
self.lastAddedOperation = operation;
}
return operation;
}
这个方法就是创建了一个request对象,然后使用这个request对象和session对象去创建下载的operation对象。
我们看一下负责单个下载任务的operation对象到底是怎么创建的,即SDWebImageDownloaderOperation
类的- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options
方法:
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_callbacksLock = dispatch_semaphore_create(1);
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
这个初始化方法其实也很简单,就是给自己的成员变量赋值
我们知道,NSOperation类的真正执行任务是在其start方法里面,那么我们看一下SDWebImageDownloaderOperation
的start
方法的具体实现:
代码比较长,我在关键部分加了注释
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
//这一部分就是解决在后台仍然进行下载的问题
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
//创建一个session对象,因为后面要创建NSURLSessionTask,需要session对象
if (!session) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
//创建dataTask,这个才是真正执行下载任务的
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
}
#pragma clang diagnostic pop
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
这里就是通过一个session对象和一个request对象创建了一个dataTask对象,这个dataTask对象才是真正用来下载的,然后调用
[self.dataTask resume]
执行下载。
到这里SDWebImage的源码分析就结束啦。
参考:
SDWebImage实现分析
这篇文章在简书的地址:SDWebImage源码解读