iOS Learning Notes - 128. SDWebImage 4 - Simple analysis of internal calls within the framework

Keywords: network xcode iOS github

SDWebImage 4 - Simple analysis of internal calls within the framework

The following comments on source code are from

Wen Ding Ding Notes.

UML Diagram and Call Sequence Diagram

Second, we started by setting up a network picture for UIImageView.

First, let's look at the way we call it ourselves.

/*Memory + disk cache*/
-(void)download{

    NSString *path = @"http://www.qqpk.cn/Article/UploadFiles/201112/20111208140318990.jpg";
    [self.imageView sd_setImageWithURL:[NSURL URLWithString:path] placeholderImage:[UIImage imageNamed:@"image1"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {

    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

    }];
}

3. sd_setImageWithURL

Now let's go to sd_setImageWithURL in the method we used above.

In fact, this method mainly does the following things

  1. Set up placeholder pictures

  2. If the url is not empty, create an operation for SDWeb Image Operation and add it to the operation cache for image loading

    The SDWebImageManager.sharedManager downloadImageWithURL is called when the operation is created, and the retrieve is completed.

    Judge the call to completedBlock based on whether there are pictures or the status we set up earlier

  3. If the url is empty, call back to the completed Block block passed in and set the image to nil

//The Core Method of Downloading Pictures
/*
 * url          Picture binary data
 * placeholder  UIImageView Occupancy Picture of ____________
 * options      Picture Download Options (Policy)
 * progressBlock    Progress callback
 * completedBlock   Finish callback
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {

    // Cancel the current image download
    [self sd_cancelCurrentImageLoad];

    // Using runtime retain url
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //Judge that if the incoming download strategy is not to delay the display of placeholder images, then set the placeholder images in the main thread
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            // Set up a placeholder image
            self.image = placeholder;
        });
    }

    //If the url is not empty
    if (url) {

        // check if activityView is enabled or not
        //Check whether activityView is available
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
         // Instantiating SDWeb Image Operation Operation
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            //Remove UIActivity Indicator View
            [wself removeActivityIndicator];
            if (!wself) return;

            //The operations in the following block are handled in the main thread
            dispatch_main_sync_safe(^{
                if (!wself) return;
                //If the picture download is completed and the incoming download option is to set the picture manually, the completedBlock callback is executed directly and returned.
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {   //Otherwise, if the picture exists, set the picture to the UI Image View and refresh the redrawn view
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    //If no image is obtained
                    //If the incoming download option is to delay the display of the placeholder image, set the placeholder image to the UI Image View and refresh the redrawn view
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        //If the url is empty, the following operations are handled in the mainline
        dispatch_main_async_safe(^{
            //Remove UIActivity Indicator View
            [self removeActivityIndicator];

            //Processing error messages and performing end-of-task callbacks to pass error messages as parameters
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            if (completedBlock) {
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

4. Download ImageWithURL

  1. Judge the url (legality, blacklist)

  2. If the URL is incorrect or the download strategy chosen is not "download failed attempt to re-download" and the URL exists in the blacklist, then return directly, call back the task to complete the block, and pass the error message.

  3. Add the current download task to the Current Executing Task Array

  4. Find out whether the image cache corresponding to URLKEY exists, and then transfer the image (existence | non-existence) and the image cache method to block after the search.
    After the cache situation is searched, the subsequent processing is carried out in the block block.

    a. Operation cancels, removes the operation added in our three steps from the "Current Executing Task Array" and does not perform the follow-up operation of the block.

    b. If the image is not cached, we call the downloadImageWithURL method of SDWebImageDownloader to create the downloadImageWithURL method to download. This callback block handles the caching of the image, callbacks the block, and removes the operations added in our three steps from the "Currently Executing Task Array".

    c. If the cached image exists, call back the block, removing the operation added in our three steps from the Current Executing Task Array

    d. Pictures are not cached and proxy downloads are not allowed. Call back completedBlock in the main thread to remove the operations added in our three steps from the Current Executing Task Array.

  5. Returns the operation created.

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    //Without completedblock, it makes no sense to call this method
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    //Check that the user's incoming URL is correct, and if the URL is of NSString type, try to convert it
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    //Prevent application crashes due to parameter type errors, and determine whether the URL is NSURL type, or if not, set it directly to nil?
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    //Initialize an SDWeb Image CombinedOperationBlock block
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;  //The initialization setting for this URL is correct

    //Add a mutex to retrieve whether the URL of the requested image is in the collection that failed to download (URL blacklist)
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }

    //If the URL is incorrect or the download strategy chosen is not "download failed attempt to re-download" and the URL exists in the blacklist, then return directly, call back the task to complete the block, and pass the error message.
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {

        //This macro ensures that the completedBlock callback is executed in the main thread
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }

    //Add the current download task to the Current Executing Task Array by adding a mutex lock
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    //Get the cache KEY corresponding to the URL
    NSString *key = [self cacheKeyForURL:url];

    //This method finds whether the image cache corresponding to URLKEY exists or not. After searching, it passes the image (existence | non-existence) and the image cache method in block mode.
    //After the cache situation has been searched, follow-up processing is performed in the block block (if the picture does not have a cache, download | if the cache exists | if the user sets the download cache strategy, how to refresh the cache, etc.)
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        //First, determine whether the download operation has been cancelled, and if cancelled, remove the current operation from the runningOperations array and return it directly.
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }

            return;
        }

        //(Image does not exist | | Download strategy is refresh cache) and (shouldDownload Image ForURL cannot respond | | The image has cache)
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
   //From here on, downloader Options (download strategy) has been handled.
            if (image && options & SDWebImageRefreshCached) {   //If the image exists, but the download strategy is to refresh the cache, notify the cache image and try to re-download it.
                dispatch_main_sync_safe(^{
                    // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                    // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }
            // download if no image or requested to refresh anyway, and download allowed by delegate
            SDWebImageDownloaderOptions downloaderOptions = 0;
            //If the download strategy is SDWeb Image LowPriority, downloader Options = itself
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (image && options & SDWebImageRefreshCached) { //If the image exists and the download strategy is refresh cache
                // force progressive off if image already cached but forced refreshing
                //If the image is cached, but the cache needs to be refreshed, the refresh is mandatory
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                //Ignore reading pictures from NSURLCache
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
  //At this location, the downloader Options (i.e., download policy) processing operation ends

            //Core Method: Use Downloader to Download Pictures
            id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
                if (weakOperation.isCancelled) {
                    //If the operation is cancelled at this time, nothing is done.
                    // Do nothing if the operation was cancelled
                    // See #699 for more details
                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
                }
                else if (error) { //If the download fails, the finished callback is processed, and the corresponding image URL is added to the blacklist where appropriate.
                    dispatch_main_sync_safe(^{
                        if (!weakOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                    });

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {//Download successful
                    //First, determine whether the current download strategy is SDWebImage RetryFailed, and if so, delete the URL from the blacklist.
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }

                    //Do you want disk caching?
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    //If the download strategy is SDWeb Image RefreshCached and the image cache exists and is not downloaded, then nothing is done.
                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    }
                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        //Otherwise, if the downloaded image exists and (not an animated image array | | the download strategy is SDWebImage Transform Animated Image & & transformDownloaded Image method is available)
                        //Open Subthread Processing
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //Immediately after download, convert the image and cache the disk and memory
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
#warning 2
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }

                            //Callback completedBlock in the main thread
                            dispatch_main_sync_safe(^{
                                if (!weakOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }
                    else {
                        //Cache the downloaded image when it is complete
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }

                        dispatch_main_sync_safe(^{
                            if (!weakOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }

                if (finished) {
                    @synchronized (self.runningOperations) {
                        [self.runningOperations removeObject:operation];
                    }
                }
            }];

            //Handling cancelBlock
            operation.cancelBlock = ^{
                [subOperation cancel];

                @synchronized (self.runningOperations) {
                    [self.runningOperations removeObject:weakOperation];
                }
            };
        }
        else if (image) {   //If the picture exists and the operation is not cancelled, call back completedBlock in the main thread and remove the current operation.
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
        else {
            // Image not in cache and download disallowed by delegate
            //If the image does not have a cache and the proxy is not allowed to download, call back the completedBlock in the main thread and remove the current operation.
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                }
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
    }];

    return operation;
}

5. Download ImageWithURL

  1. Processing download timeout, if not set, initialized to 15 seconds

  2. Create a request for NSMutableURLRequest, but not yet

  3. Call initWithRequest of SDWeb Image Downloader Operation to create an operation to download images. Download part of the method to call back our progress. After the download is completed, a series of judgments are made and callbacks are invoked.

  4. Return operation

//Core Method: Downloading Pictures
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;  //To avoid circular references to block s

    //Processing progress callback | Completion callback, etc., call the createCallback block if the url does not exist in self.URLCallbacks
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{

        //Processing download timeout, if not set, initialized to 15 seconds
        NSTimeInterval timeoutInterval = wself.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
        //Create variable request objects and set request timeouts based on a given URL and caching policy
        //Request Policy: If SDWeb Image Downloader UseNSURLCache, use NSURLRequest UseProtocolCache Policy, otherwise use NSURLRequest Reload Ignoring Local Cache Data
        /*
         NSURLRequestUseProtocolCachePolicy:Default Caching Policy
            1)If the cache does not exist, it is retrieved directly from the server.
            2)If the cache exists, the next operation will be judged according to the Cache-Control field in response. For example, if the Cache-Control field is must-revalidata, the server will be asked if the data is updated, and if it is not updated, the data will be returned directly to the user's cache. If it has been updated, the server will be requested.
         NSURLRequestReloadIgnoringLocalCacheData:Ignore the local cache data and request the server directly.
         */
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];

        //Set whether to use Cookies (using bitwise and)
        /*
         Reference on cookies: http://blog.csdn.net/chun799/article/details/17206907
         */
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        //Open the HTTP pipeline, which can significantly reduce the load time of requests, but is disabled by default because it is not widely supported by the server
        request.HTTPShouldUsePipelining = YES;

        //Setting request header information (filtering, etc.)
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }

        //Core Method: Create the Operation of Downloading Pictures
        operation = [[wself.operationClass alloc] initWithRequest:request
                                                          options:options
                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                                             SDWebImageDownloader *sself = wself;
                                                             if (!sself) return;
                                                             __block NSArray *callbacksForURL;
                                                             dispatch_sync(sself.barrierQueue, ^{
                                                                 callbacksForURL = [sself.URLCallbacks[url] copy];
                                                             });

                                                             //Traverse through all dictionaries in the callbacks ForURL array and execute the SDWeb Image Downloader ProgressBlock callback
                                                             for (NSDictionary *callbacks in callbacksForURL) {
                                                        //Note: The author of SDWeb Image Downloader ProgressBlock may have handled callbacks in the main thread, considering that users will refresh when they get progress data.
                                                                 dispatch_async(dispatch_get_main_queue(), ^{
                                                                     SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                                                                     if (callback) callback(receivedSize, expectedSize);
                                                                 });
                                                             }
                                                         }
                                                        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                                                            SDWebImageDownloader *sself = wself;
                                                            if (!sself) return;
                                                            __block NSArray *callbacksForURL;

                                                            dispatch_barrier_sync(sself.barrierQueue, ^{
                                                                callbacksForURL = [sself.URLCallbacks[url] copy];

                                                                //If done, remove the URL from the URLCallbacks dictionary
                                                                if (finished) {
                                                                    [sself.URLCallbacks removeObjectForKey:url];
                                                                }
                                                            });

                                                            //Traverse through all dictionaries in the callbacks ForURL array and execute the SDWeb Image Downloader Completed Block callback
                                                            for (NSDictionary *callbacks in callbacksForURL) {
                                                                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                                                                if (callback) callback(image, data, error, finished);
                                                            }
                                                        }
                                                        cancelled:^{
                                                            SDWebImageDownloader *sself = wself;
                                                            if (!sself) return;

                                                            //Remove the current url from the URLCallbacks dictionary
                                                            dispatch_barrier_async(sself.barrierQueue, ^{
                                                                [sself.URLCallbacks removeObjectForKey:url];
                                                            });
                                                        }];
        //Set whether decoding is required
        operation.shouldDecompressImages = wself.shouldDecompressImages;

        //identity authentication
        if (wself.urlCredential) {
            operation.credential = wself.urlCredential;
        } else if (wself.username && wself.password) {
            //Setting credentials for authentication when https is accessed
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
        }

        //Determine whether the download strategy is of high or low priority to set the queue priority of the operation?
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }

        //Add the download operation to the download queue
        //This method calls the start method inside the operation to open the download task of the picture.
        [wself.downloadQueue addOperation:operation];

        //Judge the priority of task execution. If it is LIFO, adjust the dependency of task and give priority to current (last added) task.
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [wself.lastAddedOperation addDependency:operation];

            wself.lastAddedOperation = operation;//Set the current download operation to the last one
        }
    }];

    return operation;
}

VI. initWithRequest

  1. Some operations are initialized in initWithRequest

  2. We know that the start method is called internally in NSOperation. The download operation is put in the start.

//Method of Initializing operation
- (id)initWithRequest:(NSURLRequest *)request
              options:(SDWebImageDownloaderOptions)options
             progress:(SDWebImageDownloaderProgressBlock)progressBlock
            completed:(SDWebImageDownloaderCompletedBlock)completedBlock
            cancelled:(SDWebImageNoParamsBlock)cancelBlock {
    if ((self = [super init])) {
        _request = request;
        _shouldDecompressImages = YES;
        _shouldUseCredentialStorage = YES;
        _options = options;
        _progressBlock = [progressBlock copy];
        _completedBlock = [completedBlock copy];
        _cancelBlock = [cancelBlock copy];
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
    }
    return self;
}

//Core Method: Processing Picture Download Operation in this Method
- (void)start {
    @synchronized (self) {
        //Determine whether the current operation has been cancelled, if cancelled, mark the end of the task, and process subsequent block s and cleanup operations
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

        //Conditional compilation, if it is an iphone device and larger than 4.0
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];

        //The program is about to enter the background.
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;

            //Get the UI Application singleton object
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];

            //UIBackgroundTaskIdentifier: Running programs in the background in limited time can be achieved through UIBackgroundTaskIdentifier
            //Get a certain amount of time in the background to refer to our code
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;

#warning 3
                if (sself) {
                    [sself cancel]; //Cancel the current download operation

                    [app endBackgroundTask:sself.backgroundTaskId]; //End-of-stage tasks
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif

        self.executing = YES;   //The current task is in progress

        //Create the NSURLConnection object and set up the proxy (no request is sent immediately)
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

        //Get the current thread
        self.thread = [NSThread currentThread];
    }

    [self.connection start];    //Sending network requests

    if (self.connection) {
        if (self.progressBlock) {
            //Callback to progress block
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        //Register the notification center to send notifications to SDWebImage Download Start Notification in the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });

        //Open the Runloop corresponding to the thread
        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
            // Make sure to run the runloop in our background thread so it can process downloaded data
            //Ensure that the runloop of the background thread runs
            // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
            //       not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            //Open Runloop
            CFRunLoopRun();
        }

        if (!self.isFinished) {
            [self.connection cancel];   //Cancel Network Connection
            //Handling error messages
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    else {
        //Print Connection Initialization Failed by Performing completedBlock Callback
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    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
}

Posted by sprinkles on Sun, 26 May 2019 13:13:30 -0700