preface
SDWebImage is an excellent image caching framework in ios, has experienced several versions, although the performance is not the highest, but from the point of view of security and maintenance, very suitable for most people to use – SDWebImage source code
Main Framework Introduction
The main logic is as follows
WebCache: classified information for UIView series, including call operation to cancel download, call ImageManager to obtain image content information, and processing operations after obtaining images
SDWebImageManager: A class for retrieving image content, acting as an intermediate management class to coordinate operations between cache fetching and network downloading
SDImageCache: Operation class for memory cache and disk cache. This class gets images from memory first and content from disk cache second
SDMemoryCache: Maintains the read operations of the cache
SDDiskCache: maintains and reads the disk cache
SDWebImageDownLoader: responsible for managing the image download operation, including to the SDWebImageDownloaderOperation network mapping operation request
SDWebImageDownloaderOperation: custom NSOperation subclass for more precise control of the process of images are downloaded, and network data in the process of actual receiving class for download, avoid kind of multiple data written to save problem, receive data after the callback
SDWebImage source code introduction
SDWebImage source code mainly introduces the above several classes, used to understand its core logic, in order to call the classification method as a benchmark, introduced the source code
Some tips will also be introduced for us to understand and use
WebCache classification
By categorizing the image load method calls, you end up in the following methods
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
if (context) {
// copy to avoid mutable object
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
// Save the key if there is no key
if(! validOperationKey) { validOperationKey = NSStringFromClass([selfclass]);
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
context = [mutableContext copy];
}
self.sd_latestOperationKey = validOperationKey;
// Cancel the operation subqueue corresponding to the key to avoid performance waste and error callback. The key is bound to the current View
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
if(! (options & SDWebImageDelayPlaceholder)) {// The placeholder image is not loaded lazily, and the placeholder image is loaded immediately
// This is a synchronous queue, which is determined by the label of the queue, not by the thread
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil
basedOnClassOrViaCustomSetImageBlock:setImageBlock
cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
// This is about setting the Progress callback.// Use SDWebImageManager to load images
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url
options:options context:context progress:combinedProgressBlock
completed:^(UIImage *image, NSData *data, NSError *error,
SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
if(! self) {return; }
// Mark progress completed
// if the progress not been updated, mark it to complete state
if(imageProgress && finished && ! error && imageProgress.totalUnitCount ==0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// check and stop image indicator
if (finished) {
[self sd_stopImageIndicator];
}
#endif
// Check whether callbacks and active setting of image are required according to optionsBOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage); BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) || (! image && ! (options & SDWebImageDelayPlaceholder)));// Set the callback
SDWebImageNoParamsBlock callCompletedBlockClosure = ^{
if(! self) {return; }
if(! shouldNotSetImage) {// Update view content directly, as needed
[self sd_setNeedsLayout];
}
// Call back image and data
if(completedBlock && shouldCallCompletedBlock) { completedBlock(image, data, error, cacheType, finished, url); }};if (shouldNotSetImage) {
// No need to set the image automatically, then callback in the main queue
dispatch_main_async_safe(callCompletedBlockClosure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is settargetImage = placeholder; targetData = nil; }...// Depending on the type of picture, set the picture in the main thread and call block
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[self sd_setImage:targetImage imageData:targetData
basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition
cacheType:cacheType imageURL:imageURL];
#else
[self sd_setImage:targetImage imageData:targetData
basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType
imageURL:imageURL];
#endif
callCompletedBlockClosure();
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else{#if SD_UIKIT || SD_MAC
[self sd_stopImageIndicator];
#endif
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain
code:SDWebImageErrorInvalidURL
userInfo: @ {NSLocalizedDescriptionKey : @"Image url is nil"}]; completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url); }}); }}Copy the code
dispatch_main_async_safe
The main queue is called by the label of the queue, rather than the main thread. Since the main queue is a serial queue, it is convenient to use the macro to call the block directly
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\ block(); The \}else{\ dispatch_async(dispatch_get_main_queue(), block); \ } #endifCopy the code
SDWebImageManager image loading management class
It is an image loading management class, through calling SDWebImageCache and SDWebImageDownloader to achieve cache loading first, second network download
The following method is mainly to judge whether need shelf logic, finally get into callCacheProcessForOperation method
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed: nonnull completedBlock {SDInternalCompletionBlock),...// The url failed
if (url.absoluteString.length == 0| | (! (options & SDWebImageRetryFailed) && isFailedUrl)) { NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
[self callCompletionBlockForOperation:operation
completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain
code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];
return operation;
}
SD_LOCK(_runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(_runningOperationsLock);
// Preprocess the options and context arg to decide the final the result for manager
SDWebImageOptionsResult *result = [self processedResultForURL:url
options:options context:context];
// Start the entry to load image from cache
// Start getting images
[self callCacheProcessForOperation:operation url:url
options:result.options context:result.context
progress:progressBlock completed:completedBlock];
return operation;
}
Copy the code
Load the cached
The actual loading image, caching, and network request invocation logic are in the methods below
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use
// Get the SDImageCache cache management class
id<SDImageCache> imageCache;
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// Get the query cache type
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
// Check whether we should query cache
// Determine whether to fetch from the cacheBOOL shouldQueryCache = ! SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);if (shouldQueryCache) {
// Get the key based on the URL
NSString *key = [self cacheKeyForURL:url context:context];
@weakify(operation);
// Get the cache contents from the SDImageCache class by key, as described later
operation.cacheOperation = [imageCache queryImageForKey:key
options:options context:context cacheType:queryCacheType
completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable
cachedData, SDImageCacheType cacheType) {
@strongify(operation);
// Operation does not exist or the task is cancelled
if(! operation || operation.isCancelled) {// Image combined operation cancelled by user
[self callCompletionBlockForOperation:operation completion:completedBlock
error:[NSError errorWithDomain:SDWebImageErrorDomain
code:SDWebImageErrorCancelled
userInfo: @ {NSLocalizedDescriptionKey :
@"Operation cancelled by user during querying the cache"}]
url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
} else if(context[SDWebImageContextImageTransformer] && ! cachedImage) {// Query the original graph, instead of downloading, if the original graph query failed, then start downloading, because of the logic and method basic consistent, it is not used
// Have a chance to query original cache instead of downloading
[self callOriginalCacheProcessForOperation:operation
url:url options:options context:context progress:progressBlock
completed:completedBlock];
return;
}
// Make a direct network request to download the image
// Continue download process
[self callDownloadProcessForOperation:operation url:url
options:options context:context cachedImage:cachedImage
cachedData:cachedData cacheType:cacheType progress:progressBlock
completed:completedBlock];
}];
} else {
// Direct network request
// Continue download process
[self callDownloadProcessForOperation:operation url:url
options:options context:context cachedImage:nil cachedData:nil
cacheType:SDImageCacheTypeNone progress:progressBlock
completed:completedBlock]; }}Copy the code
Download images from the Internet
The call method for downloading an image from the network is shown below
// Download process
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image loader to use
// Get the proxy class that implements the ImageLoader protocol to call the download method
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// Check whether we should download image from networkBOOL shouldDownload = ! SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly); shouldDownload &= (! cachedImage || options & SDWebImageRefreshCached); shouldDownload &= (! [self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
} else {
shouldDownload &= [imageLoader canRequestImageForURL:url];
}
// Start downloading images if needed
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
[self callCompletionBlockForOperation:operation completion:completedBlock
image:cachedImage data:cachedData error:nil cacheType:cacheType
finished:YES url:url];
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
@weakify(operation);
// Download the image method call
operation.loaderOperation = [imageLoader requestImageWithURL:url
options:options context:context progress:progressBlock completed:^(UIImage
*downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
// Cancel download failure direct callback
if(! operation || operation.isCancelled) { ... }else {
// The download is successful
// If the download succeeds, it will be removed from the failed list
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self->_failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self->_failedURLsLock);
}
// Continue store cache process
// Cache processing, including rotation image recovery, disk cache, etc
[self callStoreCacheProcessForOperation:operation url:url
options:options context:context downloadedImage:downloadedImage
downloadedData:downloadedData finished:finished
progress:progressBlock completed:completedBlock];
}
if(finished) { [self safelyRemoveOperationFromRunning:operation]; }}]; }else if (cachedImage) {
// Find the cache image, end
[self callCompletionBlockForOperation:operation completion:completedBlock
image:cachedImage data:cachedData error:nil cacheType:cacheType
finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// The image is not cached
// Image not in cache and download disallowed by delegate
[self callCompletionBlockForOperation:operation completion:completedBlock
image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES
url:url]; [self safelyRemoveOperationFromRunning:operation]; }}Copy the code
SDImageCache Fast cache management class
The first method is mainly to judge the introduction of options, mainly the following method
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context
cacheType:(SDImageCacheType)queryCacheType
done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
// There is no direct return null for key
if(! key) {if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// Invalid cache type No cache ends
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
// If not disk only, start fetching from cache memory
UIImage *image;
if(queryCacheType ! = SDImageCacheTypeDisk) { image = [self imageFromMemoryCacheForKey:key]; }// Get the image
if (image) {
// Process the image, including rotating the image (the image may be rotated)
if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image
Class animatedImageClass = image.class;
if (image.sd_isAnimated ||
([animatedImageClass isSubclassOfClass:[UIImage class]] &&
[animatedImageClass conformsToProtocol: @protocol(SDAnimatedImage))){#if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale
orientation:kCGImagePropertyOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale
orientation:image.imageOrientation];
#endif
}
} else if (options & SDImageCacheMatchAnimatedImageClass) {
// Check image class matching
Class animatedImageClass = image.class;
Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
if(desiredImageClass && ! [animatedImageClass isSubclassOfClass:desiredImageClass]) { image = nil; }}}// If only from the cache, endBOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && ! (options & SDImageCacheQueryMemoryData));if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// If the cache is not found, start caching on disk
// Second check the disk cache...
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSyncBOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) || (! image && options & SDImageCacheQueryDiskDataSync));// Define blocks for IO calls, disk access, and memory writes
void(^queryDiskBlock)(void) = ^ {if (operation.isCancelled) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}
@autoreleasepool {
// Query image data from disk
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
if (image) {
// the image is from in-memory cache, but need image data
// If an assignment is found in memory, it is returned directly
diskImage = image;
} else if (diskData) {
// The data found on the disk is saved to the cache
BOOL shouldCacheToMomery = YES;
if (context[SDWebImageContextStoreCacheType]) {
SDImageCacheType cacheType =
[context[SDWebImageContextStoreCacheType] integerValue];
shouldCacheToMomery = (cacheType == SDImageCacheTypeAll ||
cacheType == SDImageCacheTypeMemory);
}
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData
options:options context:context];
if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (shouldQueryDiskSync) {
// Call back to the IO queue as needed
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
} else {
// Call back the content in the main threaddispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, diskData, SDImageCacheTypeDisk); }); }}}};// Query in ioQueue to keep IO-safe
// Execute blocks in the IO queue
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
Copy the code
SDWebImageDownloader, SDWebImageDownloaderOperation
SDWebImageDownloader together with SDWebImageDownloaderOperation download function
The SDWebImageDownloader is responsible for queue setup and proxy callback operations for network requests
NSOperation SDWebImageDownloaderOperation inheritance, and agent SDWebImageDownloader network request operation, is responsible for downloading cancel operations such as the beginning of the request
SDWebImageDownloader
Create a new task and add it to the download queue
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
...
// Create a new task and add it to the download queue
if(! operation || operation.isFinished || operation.isCancelled) {// Set parameters such as request
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if(! operation) { SD_UNLOCK(_operationsLock);if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
returnnil; } @weakify(self); Set the callback after the completion of the queue, is used to remove information operation.com pletionBlock = ^ {@ strongify (self);if(! self) {return;
}
SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self->_operationsLock);
};
self.URLOperations[url] = operation;
[self.downloadQueue addOperation:operation];
} else {
/
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
if(! operation.isExecuting) {if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
}
SD_UNLOCK(_operationsLock);
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
Copy the code
NSURLSession Proxy operation
As can be seen below, the request takes out the subtask according to the saved queue, and the callback method is processed by the agent corresponding to the corresponding subtask, thus avoiding complex data storage and task allocation
Here introduces SDWebImageDownloader delegate is not only simple introduce to the agency callback SDWebImageDownloaderOperation
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if(completionHandler) { completionHandler(NSURLSessionResponseAllow); }}} - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) { [dataOperation URLSession:session dataTask:dataTask didReceiveData:data]; }} - (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler: (void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(proposedResponse);
}
}
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) { [dataOperation URLSession:session task:task didCompleteWithError:error]; }} - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) { [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler]; }else {
if(completionHandler) { completionHandler(request); }}} - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if(completionHandler) { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); }}} - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) { [dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics]; }}Copy the code
SDWebImageDownloaderOperation rewrite
Rewrote the start method to make better use of NSOperation
- (void)start {
@synchronized (self) {
// If the task has already been canceled, the task ends
if (self.isCancelled) {
if(! self.isFinished) self.finished = YES;// Operation cancelled by user before sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
[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:^{
[wself cancel];
}];
}
#endif
// Start the downloaded operation
NSURLSession *session = self.unownedSession;
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; self.response = cachedResponse.response; }}if(! session.delegate) {// Session been invalid and has no delegate at all
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Session delegate is nil and invalid"}]];
[self reset];
return;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
// Set task priority according to enumeration
if (self.dataTask) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
self.coderQueue.qualityOfService = NSQualityOfServiceBackground;
} else {
self.dataTask.priority = NSURLSessionTaskPriorityDefault;
self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
}
// Start the task
[self.dataTask resume];
/ / callback progress
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
});
} else {
// Task error, end
if(! self.isFinished) self.finished = YES; [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]]. [self reset]; }}Copy the code
Overriding the cancel method for NSOperation
- (void)cancel { @synchronized (self) { [self cancelInternal]; }} - (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter]
postNotificationName:SDWebImageDownloadStopNotification object:strongSelf];
});
// Cancel the task operation
if (self.dataTask) {
[self.dataTask cancel];
self.dataTask = nil;
}
// Adjust the execution state and completion state
if (self.isExecuting || self.isFinished) {
if (self.isExecuting) self.executing = NO;
if(! self.isFinished) self.finished = YES; }// Operation cancelled by user during sending the request
// End the callback
[self callCompletionBlocksWithError:
[NSError errorWithDomain:SDWebImageErrorDomain
code:SDWebImageErrorCancelled
userInfo: @ {NSLocalizedDescriptionKey :
@"Operation cancelled by user during sending the request"}]];
[self reset];
}
Copy the code
As you can see, manual KVC operations have been added to ensure that attributes trigger KVC
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
Copy the code
The last
SDWebImage download process I believe that through the introduction of the diagram can see, the author will be a function split into each module, through the management class coordination call, for example: SDWebImageManager is responsible for the management of the cache class and network request class call, SDImageCache is responsible for fast cache and disk cache processing
Download module, by overwriting NSOperation and mapping download tasks to subtasks, avoids managing data for multiple download tasks and other callbacks in a single class