The two entry points left from the previous article continue the in-depth analysis, image cache SDImageCache and image download SDWebImageDownloader

SDImageCache

SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed asynchronous so it doesn’t add unnecessary latency to the UI.

SDImageCache contains memory cache and disk cache. The disk cache write is performed asynchronously, so it does not add unnecessary latency to the UI

Find cached images

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
Copy the code
  1. determinekeyThe value of theta is essentially thetaurl.absoluteString
NSString *key = [self cacheKeyForURL:url]; // If the key does not exist, return directlyif(! key) {if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
Copy the code
  1. First look in memory: internal is actually throughNSCacheTo retrieve and store
UIImage *image = [self imageFromMemoryCacheForKey:key]; // memCache is of type NSCachereturn [self.memCache objectForKey:key];
Copy the code
  1. The operation after finding the image in which memory is fast, takes almost no time, so you don’t need an execution task to returnoperationIs nil
if(image) { NSData *diskData = nil; // If it is a GIF, check whether the array corresponding to the image is emptyif([image isGIF]) {/ / according to the key to obtain the data of the picture in the disk cache diskData = [self diskImageDataBySearchingAllPathsForKey: key]; }if (doneBlock) {// The cache is set to SDImageCacheTypeMemorydoneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
Copy the code
  1. If the memory cannot be found, it takes time to query the disk for the second time. Therefore, you need to create a taskoperation ioQueueIt’s a serial queue, where new threads are opened, execute asynchronously, and don’t block the UI
NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return; @autoreleasepool {// Get the file path by key, then get data by file path, Among them inside the file path is through the md5 encrypted NSData * diskData = [self diskImageDataBySearchingAllPathsForKey: key]; UIImage *diskImage = [self diskImageForKey:key] UIImage *diskImage = [self diskImageForKey:key];if(diskImage && self. Config. ShouldCacheImagesInMemory) {/ / to get pictures of memory size NSUInteger cost = SDCacheCostForImage (diskImage); // Find the image on disk and put it in memory so that you can use [self.memcache] directly next timesetObject:diskImage forKey:key cost:cost]; } // Return to update the UI at the end of the queryif (doneBlock) {dispatch_async(dispatch_get_main_queue(), ^{// The cache is set to SDImageCacheTypeDiskdoneBlock(diskImage, diskData, SDImageCacheTypeDisk); }); }}});return operation;
Copy the code

Cache images

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock;
Copy the code
  1. First memory cache, throughNSCachethe- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;Method,
// If the image or key does not exist, return directlyif(! image || ! key) {if (completionBlock) {
            completionBlock();
        }
        return; } / /ifMemory cache is enabled // Memory cacheif (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        
        [self.memCache setObject:image forKey:key cost:cost];
    }
Copy the code
  1. Disk cache, passNSFileManagerSave files to disk
if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            NSData *data = imageData;
            
            if(! Data && image) {// 1. png/jpeg SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data]; / / 2. The format is different, convert the data in a different way, data = [image sd_imageDataAsFormat: imageFormatFromData]; } / / 3. The disk cache, do a lot of work, internal whether IO queue, create folders, encrypted image name, whether stored up, [self storeImageDataToDisk: dataforKey:key]; // The disk cache takes time, executes the completionBlock asynchronously, and notifies the store that it has endedif(completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); }}); }Copy the code
  1. The callbackcompleteBlockNotifies the outside world that the storage is complete
if (completionBlock) {
  completionBlock();
}
Copy the code

The cache to clear

As already known from above, memory cache is using NSCache, disk cache is using NSFileManager, so cache clearance, corresponding memory clearance

[self.memCache removeObjectForKey:key]
Copy the code

Disk to remove

[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil]
Copy the code

SDWebImageDownloader

Asynchronous downloader dedicated and optimized for image loading

The optimized asynchronous downloader core method for loading images is:

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
Copy the code
  1. Method needs to return oneSDWebImageDownloadTokenObject, which corresponds to the downloader and can be used to cancel the corresponding download. It has two properties:
@property (nonatomic, strong, nullable) NSURL *url; / / the current download url address of the corresponding @ property (nonatomic, strong, nullable) id downloadOperationCancelToken; // An object of any type,Copy the code
  1. Digging into a method, the internal implementation is actually calling another method. This method is used to add each callback method block. To call this method, you need an argumentSDWebImageDownloaderOperationObject, which is created inside the block.
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL: the URL createCallback: ^ SDWebImageDownloaderOperation * {/ / / / to create internal returns a SDWebImageDownloaderOperation object SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:requestinSession:sself.session options:options]; / / returnreturn operation;
};
Copy the code
  1. SDWebImageDownloaderOperationInheritance inNSOperationTo perform the download operation. The following to createSDWebImageDownloaderOperationobject
// Set the request time limit NSTimeInterval timeoutInterval = sself.downloadTimeout;if(timeoutInterval == 0.0) {timeoutInterval = 15.0; } // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) wedisable the cache for image requests ifThe default is to prevent caching of network requests. // Create a network request request and set the network request cache policy. NSMutableURLRequest * Request = [[NSMutableURLRequest alloc] initWithURL: URL cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval]; / / whether send cookies request. HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); / / wait until returns a response, and then send the request / / YES, said don't wait for the request. The HTTPShouldUsePipelining = YES; / / request headerif (sself.headersFilter) {
       request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
   }
   else{ request.allHTTPHeaderFields = sself.HTTPHeaders; } / / initializes a photo download operation, only in the thread, or call the start will truly create SDWebImageDownloaderOperation implement request / / the real,  SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:requestinSession:sself.session options:options];
Copy the code
  1. Operation on downloadoperationAnd arrange the priority and order of download operations
/ / compressed image operation. ShouldDecompressImages = sself. ShouldDecompressImages; // Set request credentials for network requestsif (sself.urlCredential) {
       operation.credential = sself.urlCredential;
   } else if(sself.username && sself.password) { operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession]; } // Priority of operation executionif (options & SDWebImageDownloaderHighPriority) {
       operation.queuePriority = NSOperationQueuePriorityHigh;
   } else if(options & SDWebImageDownloaderLowPriority) { operation.queuePriority = NSOperationQueuePriorityLow; } // downloadQueue add download operation [sself.downloadQueue addOperation:operation]; // Add dependencies according to the order of executionif (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
       // Emulate LIFO execution order by systematically adding new operations as last operation's dependency [sself.lastAddedOperation addDependency:operation]; sself.lastAddedOperation = operation; }Copy the code
  1. In step 2 of the analysis, add methods for each callback block. The final operation of this method is returnSDWebImageDownloadTokenobject
// All downloads assume a real URL. If the URL is nil, return nilif (url == nil) {
        if(completedBlock ! = nil) { completedBlock(nil, nil, nil, NO); }returnnil; } // the method finally returns the SDWebImageDownloadToken object, declared here, and modified with __block so that it can be modified in subsequent blocks. __block SDWebImageDownloadToken *token = nil;Copy the code
  1. Use the GCD fence to ensure that dictionary writes do not conflict. There’s a property involvedURLOperationsThat type ofNSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *>Is used to store the download operation corresponding to the URL.Notice that the point A, here, corresponds to the sameurlMultiple download operations are merged into one, that is, if there are multiple sheetsImageViewCorresponds to aurl, actually perform a download operation, but their progress and completion of the block are still handled separately, followed by the arraycallbackBlocksTo store all blocks, and when the download is complete, all blocks are called back. Of course,The operation is not completeIt has not been executed yetcompletionBlock.
BarrierQueue (self.barrierQueue, ^{// a. Based on the url to judge whether there is a corresponding operation SDWebImageDownloaderOperation * operation = self URLOperations [url];if(! Operation) {/ / if there is no assignment, createCallback () is created in step 3 before SDWebImageDownloaderOperation object operation = createCallback (); Self. URLOperations[url] = operation; __weak SDWebImageDownloaderOperation *woperation = operation; / / set up after the callback operation.com pletionBlock = ^ {SDWebImageDownloaderOperation * soperation = woperation;if(! soperation)return; // The operation has finished. Remove the operationif(self.URLOperations[url] == soperation) { [self.URLOperations removeObjectForKey:url]; }}; } // Create the final object to return, Internal implementation downloadOperationCancelToken downwards see id = [operation addHandlersForProgress: progressBlock completed: completedBlock]; token = [SDWebImageDownloadToken new]; token.url = url; token.downloadOperationCancelToken = downloadOperationCancelToken; });Copy the code

SDWebImageDownloaderOperation

SDWebImageDownloaderOperation inheritance in NSOperation, and implements the < SDWebImageDownloaderOperationInterface > agreement, specially used to download operation tasks.

  1. For point 6 above, add a callback block that stores the download progress and a completion callback block. A hidden property is involvedcallbackBlocksThat type ofNSMutableArray<SDCallbacksDictionary *>Block for storing progress and completion callbacks. All callback blocks for executing tasks corresponding to urls are stored in this block.
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable CompletedBlock SDWebImageDownloaderCompletedBlock) {/ / variable dictionary typedef NSMutableDictionary < nsstrings *, id> SDCallbacksDictionary; SDCallbacksDictionary *callbacks = [NSMutableDictionary new];if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
	    if(completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; // Dispatch_barrier_async (self.barrierQueue, ^{[self.callbackblocks addObject:callbacks]; });return callbacks;
}
Copy the code
  1. whenoperationAdd to queuedownloadQueueThe call will be automatically invokedstartMethod, which resets all properties as soon as the operation is cancelled
if(self.isCancelled) { self.finished = YES; // All callback blocks are removed internally and the property is null [self reset];return;
   }
Copy the code
  1. Enter the background and continue to perform network request tasks.
#if SD_UIKIT 
        Class UIApplicationClass = NSClassFromString(@"UIApplication"); BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)]; // The request is allowed to continue after entering the backgroundif(hasApplication && [self shouldContinueWhenAppEntersBackground]) { __weak __typeof__ (self) wself = self; UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)]; / / open the background task self. BackgroundTaskId = [app beginBackgroundTaskWithExpirationHandler: ^ {__strong __typeof sself = (wself) Wself; // Background execution has a time limit. When the time expires, cancel all tasks, close background tasks, and invalidate them.if(sself) { [sself cancel]; [app endBackgroundTask:sself.backgroundTaskId]; sself.backgroundTaskId = UIBackgroundTaskInvalid; }}]; }#endif
Copy the code
  1. Create a data request task
// After iOS 7, use NSURLSession for network requests NSURLSession * Session = self.unownedSession;if(! self.unownedSession) { NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; / / request time sessionConfig. TimeoutIntervalForRequest = 15; /** * Create the sessionfor this task
        *  We send nil as delegate queue so that the session creates a serial operation queue forPerforming all delegate * method calls and completion handler calls. */ / Performing all delegate * method calls and completion handler calls. Session creates a serial operation queue, Synchronizing perform all proxy method and complete block callback self. OwnedSession = [NSURLSession sessionWithConfiguration: sessionConfig delegate: self delegateQueue:nil]; session = self.ownedSession; } / / create the data request task self. DataTask = [session dataTaskWithRequest: self. Request]; self.executing = YES;Copy the code
  1. Task start execution used here@synchronizedTo prevent other threads from accessing and processing at the same time.self.dataTaskOnly one can be created at a time
@synchronized (self) { }; // The task is executed and the request is sent [self.datatask resume];Copy the code
  1. Processing at the beginning of a task execution
if(self.datatask) {// A callback is made to all progressBlocks corresponding to the same URL.for (SDWebImageDownloaderProgressBlock progressBlock in[self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, NSURLResponseUnknownLength, self.request.URL); } // return to main thread to send notification, Dispatch_async (dispatch_get_main_queue()) ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; }); // If the task does not exist, callback error message, "request link does not exist"}else {
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
    }
Copy the code
  1. The background task does not need to exist. Disable the background task and make it invalid
#if SD_UIKIT
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(! UIApplicationClass || ! [UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {return;
    }
    if(self.backgroundTaskId ! = UIBackgroundTaskInvalid) { UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)]; [app endBackgroundTask:self.backgroundTaskId]; self.backgroundTaskId = UIBackgroundTaskInvalid; }#endif
Copy the code
  1. Data requests, various callbacks to proxy methods during the execution of the task a. The task has retrieved the complete returned data
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { /* 1. 2. Send a notification that the picture data has been received. 3. If it fails, cancel the task and reset, send notifications, and call back error messages */}Copy the code

B. Receiving network data

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { /* 1. Data splicing 2. If necessary, images are displayed section by section. 3.Copy the code

C. It is mainly used for network data caching

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {/* Perform non-network cache processing, or perform specific network cache processing */}Copy the code

D. Method called just after the last data is received

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { /* 1. Send notification, task completed, stop task 2. Picture processing and callback completionBlock 3. Mission completed */}Copy the code

Conclusion:

1. SDImageCacheA class that manages all image caching methods, including storing, retrieving, removing, and so on

2. SDWebImageDownloaderIt’s mainly used to generateSDWebImageDownloaderOperationManage the operation corresponding to the image download, and set some properties of the operation.

3. SDWebImageDownloaderOperationA class that manages data network requests and handles callback to the results of the request.