SDWebImage is a familiar name to most developers. In addition to helping us read images from the web, it also handles the caching of those images. Its cache mechanism is exactly what, let me give you nagging nagging, I hope you can have a harvest.
The basic structure
All right, let’s get started. First, let’s take a look at the overall structure of SDWebImage:
There is a special Cache category for image caching. There are also two classes SDImageCache and SDImageCacheConfig. Most of the caching is done in the SDImageCache class. We have the function of one word for the other folders. Because we are focusing on caching strategy this time, we will skip the other content for the time being.
Memory and Disk dual cache
First of all, SDWebImage uses a dual Memory and Disk Cache mechanism for image caching, which sounds pretty cool. It’s not complicated.
SDImageCache = SDImageCache;
@interface SDImageCache () #pragma mark - Properties @property (strong, nonatomic, nonnull) NSCache *memCache; .Copy the code
Here we find a property called memCache, which is an NSCache object that implements our Memory Cache for images. SDWebImage also specifically implements a class called AutoPurgeCache, which, compared to regular NSCache, provides the ability to release the cache when memory is tight:
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}Copy the code
In effect, it accepts the system’s memory warning notification and clears its image cache. One of the less common classes here is NSCache. In simple terms, it’s a collection class similar to NSDictionary that stores the data we want to cache in memory. Details you can refer to the official documentation: developer.apple.com/reference/f… .
From Memory Cache, let’s talk about Disk Cache, or file Cache.
SDWebImage stores the image in the NSCachesDirectory:
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}Copy the code
Then generate an MD5 file name for each cache file and store it in the file.
The whole mechanism
In order to save space and improve your reading experience, try not to post large sections of code here. We explained earlier that SDWebImage uses both memory and hard disk caches. So let’s take a look at the complete process when reading an image using SDWebImage. We usually use SDWebImage as an extension of UIKit to load images directly:
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcode.jpg"]];Copy the code
First, the Category method sd_setImageWithURL internally calls SDWebImageManager’s downloadImageWithURL method to handle the image URL:
id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
...
}];Copy the code
The downloadImageWithURL method inside SDWebImageManager first queries the image cache using the queryDiskCacheForKey method of the SDImageCache class we mentioned earlier:
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
...
}];Copy the code
QueryDiskCacheForKey = queryDiskCacheForKey;
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}Copy the code
If the Memory Cache is not found, the Disk Cache will be queried.
dispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); }});Copy the code
If the Disk Cache query is successful, the image is set to the Memory Cache again. Doing so maximizes the efficiency of images that are frequently displayed.
If the cache query succeeds, the cached data is returned directly. If that doesn’t work, then start requesting the network:
id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
}Copy the code
The requesting network uses the imageDownloader property, and this example is dedicated to downloading image data. If the download fails, the address of the failed image is written to the failedURLs collection:
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]; }}Copy the code
The reason for this failedURLs is that SDWebImage by default has a mechanism to refuse to reload images that failed the last time they were loaded. That is, an image fails to load in this session and is rejected if it loads again. SDWebImage is probably doing this to improve performance. This mechanic can get overlooked, so I mention it here, in case you encounter some strange problems, it will help you quickly locate the problem
If the image is downloaded successfully, it is then written to the cache using the [self.imageCache storeImage] method, and completedBlock is called to tell the front end to display the image:
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});Copy the code
Ok, so that’s it for SDWebImage’s entire image loading process. In order to control the length, I only select the most important nodes to write here, the complete mechanism of SDWebImage will definitely be more comprehensive, first to help you clear up your ideas.
Whether to retry failed URLS
We have experienced the overall image processing process of SDWebImage. So what are the important points that will help us use it? I put some together for you guys.
You can set the SDWebImageRetryFailed flag when loading images so that SDWebImage will load images that failed before. Remember the failedURLs property we mentioned earlier, which is stored in memory and if the image fails to load, SDWebImage will not retry the image during the entire APP session. Of course, this load failure is conditional, if the timeout failure, not counted.
In short, if you need image usability more than this little bit of performance optimization, you can wear the SDWebImageRetryFailed flag:
[_image sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcodexx.jpg"] placeholderImage:nil options:SDWebImageRetryFailed];Copy the code
Disk Cache clearing policy
SDWebImage performs the cleanup task at the end of each APP. The rules for clearing the cache are two-step. The first step is to purge stale cache files. If the expired cache is cleared, there is not enough space. Continue sorting by file time from morning to night, clearing the earliest cached files until the required amount of space is left.
Specifically, how does SDWebImage control which caches expire and how much space is left? Through two attributes:
@interface SDImageCache : NSObject
@property (assign, nonatomic) NSInteger maxCacheAge;
@property (assign, nonatomic) NSUInteger maxCacheSize;
Copy the code
MaxCacheAge is the duration of the file cache. SDWebImage registers two notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];Copy the code
All cached files are traversed at the end of the application and in the background, and if the cached file exceeds the duration specified in maxCacheAge, it will be deleted.
Similarly, maxCacheSize controls the maximum cache space allowed by SDImageCache. If the maxCacheSize cache does not meet the requirement after the expiration file is cleaned, the old file will continue to be cleaned until the required cache space is reached.
How does understanding this mechanism help us? So let’s move on, we don’t see them when we’re using SDWebImage. So by inference, they must have default values, and they do:
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 weekCopy the code
The above is the default value for maxCacheAge, which is clearly stated in the comment. Let’s look at maxCacheSize. Looking through the SDWebImage code, there is no default value for maxCacheSize. This means that SDWebImage does not limit the cache space by default.
This can be verified in SDWebImage cache clearing code:
If (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {// Clean up cache code}Copy the code
To clarify, the currentCacheSize variable in the above code represents the amount of space currently occupied by the image cache. As you can see, the second step of cache cleaning is only done when maxCacheSize is greater than 0 and the current cache space is greater than maxCacheSize.
What does that mean? In fact, SDWebImage by default does not set a limit on our cache size. In theory, the image cache in the APP can take up the entire device.
This feature of SDWebImage is easy to be ignored. If you develop an APP similar to information flow, you should load a large number of images. If you follow the default mechanism at this time, the cache size is unlimited, and the default cache period is one week. Therefore, the application storage space may be occupied too much.
So some people might say, nowadays iPhone has a lot of storage space, more cache is not a problem, right? But did you know that there is also a usage query feature on iOS? In the Settings, users can check the space usage of each APP. If your APP occupies a large space, it is easy to become a target for users to uninstall, which should be a detail to pay attention to.
Also, taking up too much cache space is not necessarily helpful. In most cases, some images are cached and rarely shown again. Therefore, it is necessary to plan the size of cache space reasonably. It can be set like this:
[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50MCopy the code
MaxCacheSize is represented in bytes, and our calculations above represent a maximum cache size of 50 MB. Write this line of code when your APP starts so that SDWebImage cleans up the cache when it cleans up the extra files.
conclusion
This time, I talked with you about the overall process of SDWebImage and its cache strategy. SDWebImage is an open source library that has been around for a long time and is constantly updated. Although it is not a very complicated open source library, a close look at the code shows that many of its internal mechanisms are quite clever. In order to ensure your reading experience, try to control the length of the article, here try to select the most helpful content to share with you. It took several days to structure the essay, carefully sifting through the most important points. The goal was to make it easy for him to read, but to get to the point quickly and give you useful information. I hope you can give me feedback on the reading experience and help me create better content for you.
The best place to learn more about SDWebImage is its Github page, which is also posted here for your reference: github.com/rs/SDWebIma… .
If you find this article helpful, you can also follow the wechat official account Swift-cafe and I will share more of my original content with you