Move a small bench to see SDWebImage source code parsing (2)

Keywords: Session network Attribute github

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
downloadImageWithURL method called execution flowchart

>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.

Here is a complete key flowchart of the downloaded SDWebImage pictures to give you a clearer understanding.

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.

Posted by RockRunner on Thu, 16 May 2019 15:57:25 -0700