4>SDWebImageDownloader
SDWebImageDownloader is a singleton that manages picture downloads and makes some global configurations.The following:
1). Set the maximum number of concurrencies, the default download time of 15 seconds, whether to compress pictures and download order, etc.
2). Set the request header information for the operation, responsible for generating a single SDWebImageDownloaderOperation, canceling the operation, and so on.
3). Set download policy SDWebImageDownloaderOptions.
SDWebImageDownloaderOptions
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { //Picture Download Set Low Priority SDWebImageDownloaderLowPriority = 1 << 0, //Download pictures in progress SDWebImageDownloaderProgressiveDownload = 1 << 1, //Set this option to use the NSURLCache cache policy SDWebImageDownloaderUseNSURLCache = 1 << 2, //If the picture is read from NSURLCache, the image and imageData returned to the block are set to nil SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, //After iOS4, if this option is set, pictures will still be downloaded in the background, requesting additional time for background Downloads SDWebImageDownloaderContinueInBackground = 1 << 4, // Processing cookies stored in NSHTTPCookieStore SDWebImageDownloaderHandleCookies = 1 << 5, //Allow untrusted SL certificates SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, //Set Picture Download Priority SDWebImageDownloaderHighPriority = 1 << 7, //Scale large pictures SDWebImageDownloaderScaleDownLargeImages = 1 << 8, };
SDWebImageDownloaderExecutionOrder
SDWebImageDownloaderExecutionOrder is the order in which downloads are executed:
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { //Download in queue FIFO order SDWebImageDownloaderFIFOExecutionOrder, //Download by stack LIFO SDWebImageDownloaderLIFOExecutionOrder };
SDWebImageDownloadToken
Is the unique identifier for the download operation, initializes the binding when the operation is created, and requires this token when the cancel operation is required.
@interface SDWebImageDownloadToken : NSObject @property (nonatomic, strong, nullable) NSURL *url; @property (nonatomic, strong, nullable) id downloadOperationCancelToken; @end
SDWebImageDownloader.h property method:
@interface SDWebImageDownloader : NSObject //Compress pictures @property (assign, nonatomic) BOOL shouldDecompressImages; //Set maximum concurrency @property (assign, nonatomic) NSInteger maxConcurrentDownloads; //Get the current number of Downloads @property (readonly, nonatomic) NSUInteger currentDownloadCount; //Download time defaults to 15 seconds @property (assign, nonatomic) NSTimeInterval downloadTimeout; //NSURLSession configures the policies required for some requests @property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration; //Set Download Order @property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder; //Generate a singleton and return the current instance + (nonnull instancetype)sharedDownloader; //Set up authentication @property (strong, nonatomic, nullable) NSURLCredential *urlCredential; //Username @property (strong, nonatomic, nullable) NSString *username; //Set Password @property (strong, nonatomic, nullable) NSString *password; //Blocks filtered for header s @property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter; //Generate an instance with a specific configuration sessionConfiguration - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER; //Add HTTP header for request - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field; //Returns the value of a header field - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field; //Set a subclass of SDWebImageDownloaderOperation to assign to_operationClass - (void)setOperationClass:(nullable Class)operationClass; //Load pictures asynchronously according to the specified url - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; //Cancel download of specified token - (void)cancel:(nullable SDWebImageDownloadToken *)token; //Set whether the download queue is suspended - (void)setSuspended:(BOOL)suspended; //Cancel all downloads - (void)cancelAllDownloads; //Force self to set a new NSURLSession - (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration; //Cancel operation and set session to Invalidates - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations; @end
SDWebImageDownloader.m method:
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate> //Define a download queue @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue; //The last operation defined for download is for subsequent download dependencies @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation; // Picture Download Operations Class @property (assign, nonatomic, nullable) Class operationClass; //Download url as key value is specific download operation stored in dictionary for cancel and other operations @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations; //HTTP Request Header @property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders; // This queue is used to serialize the handling of the network responses of all the download operation in a single queue //Define a queue to keep all download operation threads safe and utilize dispatch_barrier_sync queue barrierQueue for tasks @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue; //Use NSURLSession for network requests @property (strong, nonatomic) NSURLSession *session; @end
Key methods for SDWebImageDownloader.m:
//Here initialize is the point of knowledge at the beginning, but there is also a point worth learning here, NSClassFromString, to determine if the current program has a class with a specified string. If an empty object is not returned, ID activityIndicator = [NSClassFromString (@ "SDNetworkActivityIndicator") performSelector:NSSelectorFromString (@ "sharedActivityIndicat")Or')]; this works together to initialize an uncertainty + (void)initialize { // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator ) // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import if (NSClassFromString(@"SDNetworkActivityIndicator")) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")]; #pragma clang diagnostic pop // Remove observer in case it was previously added. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"startActivity") name:SDWebImageDownloadStartNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:activityIndicator selector:NSSelectorFromString(@"stopActivity") name:SDWebImageDownloadStopNotification object:nil]; } }
Initialize in the -(nonnull instance type) init; and -(nonnull instance type) initWithSessionConfiguration:(nullable NSURLSessionConfiguration *) sessionConfiguration; methods without explanation.
The downloadImageWithURL method is as follows:
//Download pictures given a specified URL - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock { __weak SDWebImageDownloader *wself = self; //Create operation in the following block return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{ __strong __typeof (wself) sself = wself; //Set Download Time 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; //Create request to set request cache policy download time NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval]; request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); //Setting HTTPShouldUsePipelining to YES allows you to request again without waiting for a response. This greatly improves the efficiency of network requests, but it can also cause problems //Because the client cannot match the request and response correctly, it depends on the server to ensure that the order of the response is consistent with the order of the client's request. If the server cannot guarantee this, the response and request may become confusing. request.HTTPShouldUsePipelining = YES; //Set Request Header if (sself.headersFilter) { request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]); } else { request.allHTTPHeaderFields = sself.HTTPHeaders; } //The episode creates an operation object here SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options]; operation.shouldDecompressImages = sself.shouldDecompressImages; //Knowledge Point Identity Authentication for Opening Lecture if (sself.urlCredential) { operation.credential = sself.urlCredential; } else if (sself.username && sself.password) { operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession]; } //Download Priority if (options & SDWebImageDownloaderHighPriority) { operation.queuePriority = NSOperationQueuePriorityHigh; } else if (options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } //Add operation to download queue [sself.downloadQueue addOperation:operation]; //Set whether downloads will be queued or stacked 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; }]; } //Package callbackBlocks, URLOperations, and token - (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(nullable NSURL *)url createCallback:(SDWebImageDownloaderOperation *(^)(void))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 nil; } __block SDWebImageDownloadToken *token = nil; //Generating URLOperations dictionary downloading url as key value is a specific download operation for easy download cancel and so on dispatch_barrier_sync(self.barrierQueue, ^{ SDWebImageDownloaderOperation *operation = self.URLOperations[url]; if (!operation) { operation = createCallback(); self.URLOperations[url] = operation; __weak SDWebImageDownloaderOperation *woperation = operation; operation.completionBlock = ^{ dispatch_barrier_sync(self.barrierQueue, ^{ SDWebImageDownloaderOperation *soperation = woperation; if (!soperation) return; if (self.URLOperations[url] == soperation) { [self.URLOperations removeObjectForKey:url]; }; }); }; } //Encapsulate the progress progressBlock and the download end completedBlock into a dictionary, SDCallbacksDictionary, and load the array callbackBlocks. id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; //The token identity is generated here, and this token says simply to cancel it by calling [self.imageDownloader cancel:subOperationToken] in SDWebImageManager token = [SDWebImageDownloadToken new]; token.url = url; token.downloadOperationCancelToken = downloadOperationCancelToken; }); return token; }
Other methods in SDWebImageDownloader are simpler, such as the following. Not to mention them alone, you can understand them at a glance.
- (void)setSuspended:(BOOL)suspended; - (void)cancelAllDownloads ; - (NSInteger)maxConcurrentDownloads
>5 SDWebImageDownloaderOperation
SDWebImageDownloaderOperation inherits from NSOperation and is the specific unit that performs image downloads.Responsible for generating NSURLSessionTask for picture requests, supporting download cancellations and background downloads, reporting download progress in time during download, decoding, zooming and compressing pictures after successful download.
Specific comments for the SDWebImageDownloaderOperation.h file:
//Start downloading notifications FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification; //Receive response notification FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification; //Stop Downloading Notification FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification; //End download notification FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification; //If you want to use a custom downloader object, this object needs to inherit from NSOperation and implement this protocol @protocol SDWebImageDownloaderOperationInterface<NSObject> //Initialization method of SDWebImageDownloaderOperationInterface - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options; //Bind DownloaderOperation Download Progress block and End block - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; //Returns whether pictures from the current download should be compressed - (BOOL)shouldDecompressImages; //Set whether pictures should be compressed - (void)setShouldDecompressImages:(BOOL)value; //Return to Authentication - (nullable NSURLCredential *)credential; //Set up authentication - (void)setCredential:(nullable NSURLCredential *)value; @end @interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate> //Request request for operation @property (strong, nonatomic, readonly, nullable) NSURLRequest *request; //task of operation @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; //Can pictures be compressed @property (assign, nonatomic) BOOL shouldDecompressImages; /** * Was used to determine whether the URL connection should consult the credential storage for authenticating the connection. * @deprecated Not used for a couple of versions */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility"); //identity authentication @property (nonatomic, strong, nullable) NSURLCredential *credential; //Download Configuration Policy @property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options; //Expected size of data @property (assign, nonatomic) NSInteger expectedSize; //response of operation @property (strong, nonatomic, nullable) NSURLResponse *response; //Initialize an SDWebImageDownloaderOperation object - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session //Bind DownloaderOperation Download Progress block and End block - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; //Protocol Approach for SDWebImageOperation - (BOOL)cancel:(nullable id)token; @end
Specific comment file for SDWebImageDownloaderOperation.m:
//Start download notification assignment NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification"; //Receive response notification assignment NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification"; //Stop Download Notification Assignment NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; //End download notification assignment NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; //Static global variables as key s stored in the download progress block dictionary static NSString *const kProgressCallbackKey = @"progress"; //Static global variables as key s to end downloading the block dictionary static NSString *const kCompletedCallbackKey = @"completed"; //Declare type SDCallbacksDictionary key=NSString, value=id typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary; @interface SDWebImageDownloaderOperation () @property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks; //Is Executing @property (assign, nonatomic, getter = isExecuting) BOOL executing; //Do you want to end the download? @property (assign, nonatomic, getter = isFinished) BOOL finished; //Image Data for Pictures @property (strong, nonatomic, nullable) NSMutableData *imageData; //Cached data @property (copy, nonatomic, nullable) NSData *cachedData; //Network requests session, note that the attribute modifier used here is weak because unownedSession is passed from SDWebImageDownloader, so SDWebImageDownloader management is required @property (weak, nonatomic, nullable) NSURLSession *unownedSession; //Network request session, note that the attribute modifier used here is strong, if unownedSession is nil, we need to manually create and manage its life cycle and proxy method @property (strong, nonatomic, nullable) NSURLSession *ownedSession; //Network Request Specific task @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask; //Global Parallel Queue, Control Data @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue; #if SD_UIKIT //If the user sets the option to continue loading in the background, continue downloading pictures through backgroundTask @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; #endif //Picture decoder, interesting is that if the picture is not fully downloaded, you can also decode some of the display pictures @property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder; @end @implementation SDWebImageDownloaderOperation @synthesize executing = _executing; @synthesize finished = _finished; //init method - (nonnull instancetype)init { return [self initWithRequest:nil inSession:nil options:0]; } //Initialization method, assignment configuration of some basic data - (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; _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT); } return self; } - (void)dealloc { SDDispatchQueueRelease(_barrierQueue); } //Bind DownloaderOperation Download Progress block and End block - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock { SDCallbacksDictionary *callbacks = [NSMutableDictionary new]; if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; //Using the dispatch_barrier_async method to asynchronously block the current thread, serially perform the operations added to the array dispatch_barrier_async(self.barrierQueue, ^{ [self.callbackBlocks addObject:callbacks]; }); return callbacks; } //Get the callback blocks for all corresponding keys in the callback block array based on the key - (nullable NSArray<id> *)callbacksForKey:(NSString *)key { __block NSMutableArray<id> *callbacks = nil; //Synchronized execution, blocking current thread and queue dispatch_sync(self.barrierQueue, ^{ // We need to remove [NSNull null] because there might not always be a progress block for each callback //We need to delete the [NSNull null] element from the array because there may be no callback block callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy]; [callbacks removeObjectIdenticalTo:[NSNull null]]; }); return [callbacks copy]; // strip mutability here } //Cancel Method - (BOOL)cancel:(nullable id)token { __block BOOL shouldCancel = NO; //Synchronization method blocks current queue and thread dispatch_barrier_sync(self.barrierQueue, ^{ //Delete the token object from the callback block array in the array, where token is a key string and value is a dictionary of block [self.callbackBlocks removeObjectIdenticalTo:token]; //Determine if the array is 0. Cancel the download task if (self.callbackBlocks.count == 0) { shouldCancel = YES; } }); if (shouldCancel) { [self cancel]; } return shouldCancel; } //Overrides the start method of the operation, which is executed when the task is added to the NSOperationQueue, starting the download task - (void)start { //Add synchronous locks to prevent multithreaded data competition @synchronized (self) { if (self.isCancelled) { self.finished = YES; [self reset]; return; } #if SD_UIKIT //If the caller has configured to continue downloading pictures in the background, continue downloading here 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 //Get cached data for network requests if (self.options & SDWebImageDownloaderIgnoreCachedResponse) { // Grab the cached data for later check NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]; if (cachedResponse) { self.cachedData = cachedResponse.data; } } NSURLSession *session = self.unownedSession; //Determine if unownedSession is nil or recreate ownedSession if it is nil if (!self.unownedSession) { NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; //Timeout 15s sessionConfig.timeoutIntervalForRequest = 15; //delegateQueue is nil, so the callback method defaults to executing in a serial queue with a child thread self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; session = self.ownedSession; } //Use session to create a NSURLSessionDataTask type download task self.dataTask = [session dataTaskWithRequest:self.request]; self.executing = YES; } //Start Task Execution [self.dataTask resume]; if (self.dataTask) { //Task on, traverse progress block array execute first download progress progress 0 for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, NSURLResponseUnknownLength, self.request.URL); } __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ //Notifications are sent in the main thread and self s are passed out. Why are they sent in the main thread? //Since notifications are received on which thread they are sent, there is no problem performing UI modification operations in the notification-accepting method. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf]; }); } else { //Execute a failed callback block if tataTask creation fails [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]]; } #if SD_UIKIT //Continue downloading in the background 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 } //Protocol Approach for SDWebImageOperation - (void)cancel { @synchronized (self) { [self cancelInternal]; } } //Cancel Download - (void)cancelInternal { if (self.isFinished) return; [super cancel]; //Cancel cancel immediately if the task to download pictures is still in progress and send a notification to end the download if (self.dataTask) { [self.dataTask cancel]; __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf]; }); // As we cancelled the connection, its callback won't be called and thus won't // maintain the isFinished and isExecuting flags. if (self.isExecuting) self.executing = NO; if (!self.isFinished) self.finished = YES; } [self reset]; } //Method invoked after download is complete - (void)done { self.finished = YES; self.executing = NO; [self reset]; } //Method of resetting data - (void)reset { __weak typeof(self) weakSelf = self; //Remove all elements of the callback block dictionary array dispatch_barrier_async(self.barrierQueue, ^{ [weakSelf.callbackBlocks removeAllObjects]; }); self.dataTask = nil; NSOperationQueue *delegateQueue; //Get delegateQueue from unownedSession if it exists, otherwise get it from ownedSession if (self.unownedSession) { delegateQueue = self.unownedSession.delegateQueue; } else { delegateQueue = self.ownedSession.delegateQueue; } //If there is a proxy method execution queue if (delegateQueue) { //delegateQueue must be a serial queue with a maximum concurrency of 1 NSAssert(delegateQueue.maxConcurrentOperationCount == 1, @"NSURLSession delegate queue should be a serial queue"); [delegateQueue addOperationWithBlock:^{ weakSelf.imageData = nil; }]; } //If ownedSession exists, invalidateAndCancel is called manually for the task if (self.ownedSession) { [self.ownedSession invalidateAndCancel]; self.ownedSession = nil; } } //stter for finished attribute - (void)setFinished:(BOOL)finished { //Manually trigger KVO notifications [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } //stter for executing attribute - (void)setExecuting:(BOOL)executing { //Manually trigger KVO notifications [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } //Override the NSOperation method to identify that this is a concurrent task - (BOOL)isConcurrent { return YES; } #pragma mark NSURLSessionDataDelegate //Receive a response from the server and execute only once in a request - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { //'304 Not Modified' is an exceptional one //Determine the status of the request based on the status code if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) { //Get the length of the picture to download NSInteger expected = (NSInteger)response.expectedContentLength; expected = expected > 0 ? expected : 0; //Set current expectedSize self.expectedSize = expected; //Traverse the progress callback block and trigger it for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, expected, self.request.URL); } //Create variable data based on file size returned by response self.imageData = [[NSMutableData alloc] initWithCapacity:expected]; self.response = response; __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ //Send a notification to accept a response on the main thread [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf]; }); } else { //Enter a failed situation NSUInteger code = ((NSHTTPURLResponse *)response).statusCode; //This is the case when server returns '304 Not Modified'. It means that remote image is not changed. //In case of 304 we need just cancel the operation and return cached image from the cache. //If code equals 304, cancel the download directly, otherwise execute the URLSession:task:didCompleteWithError:proxy method if (code == 304) { [self cancelInternal]; } else { [self.dataTask cancel]; } __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ ///Main thread sends notification to end Download [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf]; }); //Execute Code Block with Download Failure Exception [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]]; //Perform some actions to end the download [self done]; } if (completionHandler) { completionHandler(NSURLSessionResponseAllow); } } //Trigger every time data is received, possibly multiple times - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { //Add received data to variable data [self.imageData appendData:data]; //If the caller configures progressive download support, that is, show the downloaded portion and expectedSize returns a picture with size greater than 0 if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) { // Get the image data NSData *imageData = [self.imageData copy]; // Get the total bytes downloaded const NSInteger totalSize = imageData.length; // Get the finish status //Determine if the download is complete BOOL finished = (totalSize >= self.expectedSize); //Create a new one if there is no decompressed object if (!self.progressiveCoder) { // We need to create a new instance for progressive decoding to avoid conflicts for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) { if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] && [((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) { self.progressiveCoder = [[[coder class] alloc] init]; break; } } } //Converting imageData to image UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished]; if (image) { //Get cached key from URL NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; //Scale pictures image = [self scaledImageForKey:key image:image]; //If the caller chooses to compress the picture, then perform the picture compression here. Note here that the incoming data is a **, pointer-to-pointer pointer, represented by &data if (self.shouldDecompressImages) { image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}]; } //Make a failed callback [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO]; } } //Code block callback for download progress for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(self.imageData.length, self.expectedSize, self.request.URL); } } //Do some work if you need to cache response - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { NSCachedURLResponse *cachedResponse = proposedResponse; if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) { // Prevents caching of responses cachedResponse = nil; } if (completionHandler) { completionHandler(cachedResponse); } } #pragma mark NSURLSessionTaskDelegate //Callback methods when download completes or fails - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { @synchronized(self) { self.dataTask = nil; __weak typeof(self) weakSelf = self; //Return to main thread to send download end notification dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf]; if (!error) { [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf]; } }); } //Download failures go away with failed callbacks if (error) { [self callCompletionBlocksWithError:error]; } else { //Determine if the number of download callback end blocks is greater than 0 if ([self callbacksForKey:kCompletedCallbackKey].count > 0) { /** * If you specified to use `NSURLCache`, then the response you get here is what you need. */ NSData *imageData = [self.imageData copy]; //If Picture Exists if (imageData) { /** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`, * then we should check if the cached data is equal to image data */ //Determines if the call is configured to use caching, if it is, whether the cached data is consistent with the current picture, and if it is consistent, the callback ends if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) { // call completion block with nil [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES]; } else { //Decoding Picture data UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData]; //Get cached key from picture url NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; //Scale pictures image = [self scaledImageForKey:key image:image]; BOOL shouldDecode = YES; // Do not force decoding animated GIFs and WebPs //No compression for GIF and WebP pictures if (image.images) { shouldDecode = NO; } else { #ifdef SD_WEBP //The sd_imageFormatForImageData classification method determines which picture format SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData]; if (imageFormat == SDImageFormatWebP) { shouldDecode = NO; } #endif } //If the format is compressible and the caller has set compression, start compressing the picture here if (shouldDecode) { if (self.shouldDecompressImages) { BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages; image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}]; } } if (CGSizeEqualToSize(image.size, CGSizeZero)) { [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]]; } else { [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES]; } } } else { //No pictures end with direct callback [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]]; } } } //Operation after completion [self done]; } //Processing for certificates returned by the server requires telling the system in this method whether the certificates returned by the server need to be installed - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; // Determine if the certificate returned by the server is trusted by the server? if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } else { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; disposition = NSURLSessionAuthChallengeUseCredential; } } else { if (challenge.previousFailureCount == 0) { if (self.credential) { credential = self.credential; disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } if (completionHandler) { completionHandler(disposition, credential); } } #pragma mark Helper methods //Call an inline function internally - (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image { return SDScaledImageForKey(key, image); } //Determine whether background downloads are supported - (BOOL)shouldContinueWhenAppEntersBackground { return self.options & SDWebImageDownloaderContinueInBackground; } //Callback block - (void)callCompletionBlocksWithError:(nullable NSError *)error { [self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES]; } //Traversing callbacks code block array - (void)callCompletionBlocksWithImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData error:(nullable NSError *)error finished:(BOOL)finished { NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey]; dispatch_main_async_safe(^{ for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) { completedBlock(image, imageData, error, finished); } }); } @end
The specific download process is described in words:
1. First generate the SWebImageDownloaderOperation inherited from NSOperation and configure the current operation.
2. Adding an operation to the NSOperationQueue download queue triggers the start method of the operation.
3. If isCancelled for operation is found to be YES, indicating that it has been canceled, finish=YES ends the download.
4. Create NSURLSessionTask to execute resume and start downloading.
5. Determine the status of the request based on the code when the server receives the response. If it is normal, send a notification that the response is being accepted and the download progress.cancel download if 304 or other abnormal state.
6. Append pictures of the currently downloaded data to the variable data each time didReceiveData receives a return response from the server and report on the progress of the download.
7. At the end of didCompleteWithError download, if the download succeeds in decoding the picture data, zooming or compressing the picture, send a download end notification.Download failed execution failed callback.
The level of bloggers is limited, so it is unavoidable to make mistakes. If you have any questions, please don't hesitate to give them advice.