Common Problems about SDWebImage Source Code

Posted by grandeclectus on Sun, 30 Jun 2019 01:38:08 +0200


SDWebImage.png

analysis

Some time ago, I talked to a young man who asked me:
Boy: "What modification does NSString use when declaring attributes?"
Me: "copy"
Boy: "Why copy? What's wrong with strong?"
Me: "If you use strong modifier, you just make a shallow copy of the string. When an object holds this property, it changes the value of the property."
Boy: "Then I want to change it?"
Me: "... () o)?"

It's been three years since I came out and planted it on the most basic one. It's embarrassing. In fact, in the final analysis, it is not enough to do internal work. Dragonfly water is a big taboo for developers. It's really a frog in the bottom of the well to make a few apps and feel how you feel.
As an iOS developer, I believe that everyone will use or understand SDWebImage more or less, and there are not a few articles to analyze its source code. Today, I simply comb the SDWebImage I understand from the point of view of problem-driven.

SDWeb Images Picture Type Recognition

As we all know, UIImageView can only load png-type pictures by default, and jpg/gif-type pictures are loaded separately. How can SDWebImage identify the type of network pictures?
Read the source code, you will find that NSData classification file NSData+ImageContentType.m, it is based on the file header to identify, that is, the first byte of the image stream file judgment.

#import "NSData+ImageContentType.h"
@implementation NSData (ImageContentType)

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}
@end

OpenCV image type recognition is similar, see: Incude1224 Blog: Read the header to determine the type of picture

Download queue mechanism of SDWebImage

SDWebImage loads network images asynchronously. Whether in terms of performance or traffic savings for users, SDWebImage does a good job.
Then the question arises:
1. How does SDWebImage process multiple images when they are loaded asynchronously? Is there a corresponding concurrent queue?
2. If so, what is its concurrent queue mechanism? Since it is a concurrent queue, what is the maximum number of concurrent queues?
3. When the task of downloading multiple pictures ends, what is the strategy of removing them from the queue, first in first out? Or last in first out?
4. When the URL of an image is an incorrect link, or the server is abnormal, or the network is abnormal, does SDWebImage have an abnormal timeout? If there is an overtime mechanism, how long will it take?
Next, I will find out the answers for you one by one:
SDWebImage Network Picture Download is accomplished by SDWebImage Downloader and SDWebImage Downloader Operation classes.

  • SDWeb Image Downloader Operation encapsulates a single image download operation. It has a start method to open a download task. Looking at the source code, you can see that there is a section in the start method body:
    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    sessionConfig.timeoutIntervalForRequest = 15;
    Internally, the timeout time for a single task is explicitly written in 15 seconds.
  • SDWeb Image Downloader is used to manage SDWeb Image Downloader Operations image download tasks (in addition, task timeout can also be configured in SDWeb Image Downloader). It holds several public attributes: maxConcurrent Downloads (maximum concurrency), download Timeout (task timeout), execution Order (queue execution mode), etc., and maintains a private concurrency. Download the queue download Queue and a latest task add task lastAddedOperation.
    Looking at the source code, we can easily understand that the download queue of SDWeb Image is SDWeb Image Downloader FIFO Execution Order by default, which is first-in-first-out, and the number of concurrent download queues is 6.

    downloadQueue.maxConcurrentOperationCount = 6;
    downloadTimeout:  15.0;
    executionOrder: SDWebImageDownloaderFIFOExecutionOrder;
  • See the source code for details:

@interface SDWebImageDownloader () 
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
......
@end
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
    if ((self = [super init])) {
        _operationClass = [SDWebImageDownloaderOperation class];
        _shouldDecompressImages = YES;
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
        _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
        _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
        _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;
        sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
        /**
         *  Create the session for this task
         *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
         *  method calls and completion handler calls.
         */
        self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                     delegate:self
                                                delegateQueue:nil];
    }
    return self;
}

SDWebImage Caching Mechanism

SDWebImage caching mechanism actually consists of two parts: memory caching and disk caching. We can see this clearly from SDImageCache files, where memCache is memory cache, diskCachePath is disk cache, and data files are stored in sandbox:

@interface SDImageCache ()
@property (strong, nonatomic, nonnull) NSCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
......
@end

Memory cache
In order to improve the memory cache, SDWebImage implements AutoPurgeCache, a subclass of NSCache, which extends NSCache. When memory alerts, it will accept UI Application DidReceive Memory Warning Notification notification and automatically execute removeAllObjects operation.

@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;
}
- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end

Disk Cache
Next, let's talk about disk caching. Disk caching files are stored in sandboxes, and the stored procedures are complex. Let me start with a brief description of the general process of loading images by SDWebImage, from which we believe you will know something about diskCache.
When using SD Web Image, we usually start with UI ImageView + WebCache file. The first step of using SD Web Image is to introduce the classified WebCache of UI ImageView, and then call sd_setImageWithURL: method to complete the asynchronous loading of images.
The specific process of image loading is as follows:

  • When calling the sd_setImageWithURL method, it first queries the memory cache through the URL as the key, that is, the memCache attribute of SDImageCache, which is displayed directly on the View if it exists.
  • Conversely, the file name will be md5 encoding URL, and the file in the sandbox (i.e. under the diskCachePath path of SDImageCache) will be queried. If it exists, the file in the sandbox will be loaded into the memory cache memCache, and then decoded by SDWebImage Decoder and displayed directly on the View.
  • If the sandbox does not exist, the placeholder image is loaded onto the View first, and then the download Queue queue of SDWeb Image Downloader is queued to find out if there is a download task downloading the image, and if there is one, continue the task.
  • If the download queue does not exist, create SDWebImage Downloader Operation, then add it to the download queue through lastAddedOperation according to the corresponding mechanism. After download, remove the operation from the queue, add the image to the memory cache, display it directly to View, and store the file in sandbox after compression and coding. The md5 encoded URL is used as the file name.

I believe that after seeing the above process, we have some knowledge of disk caching mechanism, of course, it also brings some questions, such as:
1. Why do image files use md5-encoded URL s as file names?
2. Disk caching, since it is called caching, must have a certain period of time. What is the length of caching?
3. When will the expired image files be cleared after the expiration?
4. Sandbox size is limited. Is there a size limit for the disk space reserved for SDWebImage?
5. How do I clear all SDWebImage caches if I want to empty them? What if we need to clear specific image caches?
Below, I will answer this series of questions for you one by one:

SDWebImage Cached Picture Naming Problem

How does SDWebImage maintain cached images? In the SDIMAGE Cache file, we can easily find that it uses the compressibility, easy calculation, strong anti-collision and other characteristics of MD5 to encode the URL s of pictures in MD5 and store them as file names in sandboxes.

MD5 Baidu Encyclopedia
MD5 algorithm has the following characteristics:
1. Compressibility: For any length of data, the calculated MD5 value length is fixed.
2. Easy to calculate: It's easy to calculate MD5 from the original data.
3. Resistance to modification: Any modification of the original data, even if only one byte is modified, the MD5 values obtained are very different.
4. Strong anti-collision: It is very difficult to find a data with the same MD5 value (i.e. forged data) when the original data and its MD5 value are known.

- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
    NSString *filename = [self cachedFileNameForKey:key];
    return [path stringByAppendingPathComponent:filename];
}
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
    return [self cachePathForKey:key inPath:self.diskCachePath];
}
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
    const char *str = key.UTF8String;
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                          r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
    return filename;
}
SDWebImage Cache File Preservation Time and Cache Space Size

Since it is a cache, there must be a corresponding time limit. By default, the cache time of SDWebImage is one week, and the cache space can be customized.

#import "SDImageCacheConfig.h"
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
@implementation SDImageCacheConfig

- (instancetype)init {
    if (self = [super init]) {
        _shouldDecompressImages = YES;
        _shouldDisableiCloud = YES;
        _shouldCacheImagesInMemory = YES;
        _maxCacheAge = kDefaultCacheMaxCacheAge;
        _maxCacheSize = 0;
    }
    return self;
}
@end
Clear specific image caches

As I just said, SDWebImage loaded images are cached and stored for a week by default. When using SDWeb Image to load images with the same URL, preference will be taken from the cache rather than every re-request to load. Then the problem arises. Our avatar/advertisement maps need to be refreshed in real time. We need to clear specific image caches.
As far as the updating problem of avatar/advertisement picture is concerned, it is nothing more than updating the cache problem. There are many ways to solve it.

  • Use options: SDWebImage RefreshCached to refresh the cache, but some children's shoes react that the method has flickering problems, and sometimes does not even update the image, so it's better to use manual caching to be safe.
  • Each time the image cache is removed and reloaded, the code is as follows:
    NSURL *imageURL = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/949086-5d2c51f1e3a9cddd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/999"];
// Get the key of the corresponding URL link
    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:imageURL];
    NSString *pathStr = [[SDImageCache sharedImageCache] defaultCachePathForKey:key];
    NSLog(@"key Storage path: %@", pathStr);
// Delete the file corresponding to the key
    [[SDImageCache sharedImageCache] removeImageForKey:key withCompletion:^{
        [self.tempImageView sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"placeholderHead.png"]];
    }];
Timing to Clear Outdated Documents

From the above answers, you know that disk cached files have a time limit, so when does SDWebImage clear expired files? In the SDIMAGE Cache file, we can also get the answer:
There are two time points for clearing out outdated files: when the program cuts to the background and kills the APP.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteOldFiles) name:UIApplicationWillTerminateNotification object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundDeleteOldFiles) name:UIApplicationDidEnterBackgroundNotification object:nil];

The specific source code is as follows:

@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;
}
- (void)dealloc {
#if SD_UIKIT
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end

This article has been registered in copyright printing. If you need to reproduce it, please obtain authorization in copyright printing.
Access to Copyright

Topics: network Session encoding iOS