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
- determine
key
The 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
- First look in memory: internal is actually through
NSCache
To retrieve and store
UIImage *image = [self imageFromMemoryCacheForKey:key]; // memCache is of type NSCachereturn [self.memCache objectForKey:key];
Copy the code
- The operation after finding the image in which memory is fast, takes almost no time, so you don’t need an execution task to return
operation
Is 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
- If the memory cannot be found, it takes time to query the disk for the second time. Therefore, you need to create a task
operation
ioQueue
It’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
- First memory cache, through
NSCache
the- (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
- Disk cache, pass
NSFileManager
Save 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
- The callback
completeBlock
Notifies 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
- Method needs to return one
SDWebImageDownloadToken
Object, 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
- 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 argument
SDWebImageDownloaderOperation
Object, 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
SDWebImageDownloaderOperation
Inheritance inNSOperation
To perform the download operation. The following to createSDWebImageDownloaderOperation
object
// 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
- Operation on download
operation
And 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
- In step 2 of the analysis, add methods for each callback block. The final operation of this method is return
SDWebImageDownloadToken
object
// 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
- Use the GCD fence to ensure that dictionary writes do not conflict. There’s a property involved
URLOperations
That type ofNSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *>
Is used to store the download operation corresponding to the URL.Notice that the point A, here, corresponds to the sameurl
Multiple download operations are merged into one, that is, if there are multiple sheetsImageView
Corresponds to aurl
, actually perform a download operation, but their progress and completion of the block are still handled separately, followed by the arraycallbackBlocks
To 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.
- For point 6 above, add a callback block that stores the download progress and a completion callback block. A hidden property is involved
callbackBlocks
That 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
- when
operation
Add to queuedownloadQueue
The call will be automatically invokedstart
Method, 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
- 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
- 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
- Task start execution used here
@synchronized
To prevent other threads from accessing and processing at the same time.self.dataTask
Only one can be created at a time
@synchronized (self) { }; // The task is executed and the request is sent [self.datatask resume];Copy the code
- 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
- 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
- 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. SDImageCache
A class that manages all image caching methods, including storing, retrieving, removing, and so on
2. SDWebImageDownloader
It’s mainly used to generateSDWebImageDownloaderOperation
Manage the operation corresponding to the image download, and set some properties of the operation.
3. SDWebImageDownloaderOperation
A class that manages data network requests and handles callback to the results of the request.