In the previous article, we have learned about the implementation mechanism and elimination strategy of NSCache. Since the article is too long, it is not conducive to reading. So in this article, we will explore and explain the cache processing mechanism in NSURLCache and SDWebImage.

The table of contents I will continue the previous article, in order to compare the reading.

2. NSURLCache

2.1 introduction

First send the official document to NSURLCache

First of all, we all know that when using NSURLCache to cache request data, there is also a default cache processing. So what do we need to do? What does the native default do?

The NSURLCache class implements the caching of responses to URL load requests by mapping NSURLRequest objects to NSCachedURLResponse objects. It provides a composite in-memory and on-disk cache, and lets you manipulate the sizes of both the in-memory and on-disk portions. You can also control the path where cache data is stored persistently.

What does that mean? The important thing is that it provides disk cache and memory cache, and lets the user specify the size of the disk and memory cache. It is not important to know when the contents of the cache on disk or in memory will be known.

And it allows us to focus on different strategies to meet flexible and changing needs.

2.1.1 Cache Policy

When using NSURLSession, we can specify NSURLRequestCachePolicy directly via NSMutableURLRequest.

typedef NS_ENUM(NSUInteger.NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0.// Default policy

    NSURLRequestReloadIgnoringLocalCacheData = 1.// Ignore cache, must download from remote address;
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4.// Unimplemented not implemented
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData.NSURLRequestReturnCacheDataElseLoad = 2.// Whether the cache expires or not, use the cache if it does, and request data if it does not.
    NSURLRequestReturnCacheDataDontLoad = 3.// Whether the cache expires or not, use the cache if it does, and fail if it does not.

    NSURLRequestReloadRevalidatingCacheData = 5.// Unimplemented not implemented
};
Copy the code

Before we get to NSURLCache, let’s take a look at the default HTTP caching mechanism. This is the default policy for NSURLRequestCachePolicy.

2.1.2 HTTP Cache Policy

Let’s start with an example that uses the HTTP caching policy by default.

- (void)example_1{
    NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/50x50.jpg"];
    // The HTTP cache policy is used by default
     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error warning : %@",error);
        }else{
            // Read data from cache!
            NSData *tempData = data;
            NSString *responseStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
            NSLog(@"response:%@",response);
        }
    }] resume];
    
}
Copy the code

Print result:

response:<NSHTTPURLResponse: 0x600002ff7380> { URL: http://via.placeholder.com/50x50.jpg } { Status Code: 200, Headers {
    "Accept-Ranges" =     (
        bytes
    );
    "Cache-Control" =     (
        "max-age=604800"
    );
    Connection =     (
        "keep-alive"
    );
    "Content-Length"= (807);"Content-Type" =     (
        "image/jpeg"
    );
    Date =     (
        "Wed, 18 Sep 2019 06:32:38 GMT"
    );
    Etag =     (
        "\"5d5c8aae-327\""
    );
    Expires =     (
        "Wed, 25 Sep 2019 06:32:38 GMT"
    );
    "Last-Modified" =     (
        "Wed, 21 Aug 2019 00:05:02 GMT"
    );
    Server =     (
        "Nginx / 1.6.2"
    );
    "X-Cache"= ( L1 ); }}Copy the code

Pragma and cache-Control are two fields that Control the Cache switch in HTTP. Pragma is an old artifact that has been gradually abandoned, and some sites still use these two fields for backward compatibility. I won’t introduce it here.

Cache-Control

When cache-control is used in a request, its optional values are:

Cache invalidation

In caching, we need a mechanism to verify that the cache is valid. For example, when the server resources are updated, the client needs to refresh the cache in time. Alternatively, the resource on the client expires, but the resource on the server is still old. In this case, resending the resource does not need to be done. Cache validation is used to solve these problems,

In HTTP 1.1, we focused on last-Modified and eTAG fields.

Last-Modified

When a server returns a resource, it returns the Last Modified time of the resource to the client in the last-Modified field. If-modified-since or if-unmodified-since with last-modified is the next request from the client, and the server checks whether the time is consistent with the Last Modified time of the server:

  • If they are the same, the 304 status code is returned, and the resource is not returned.
  • If not, return 200 and the modified resource with the new time.

The difference between if-modified-since and if-unmodified-since is:

If-modified-since: tells the server to return status code 304 If the time is consistent

If-unmodified-since: tells the server to return status code 412 If the time is inconsistent

Etag

Judging by the modification time alone is still flawed, for example, the last modification time of the file has changed, but the content has not changed. For such cases, we can use Etag.

Etag mechanism:

The server calculates the resource using an algorithm and obtains a string of values (similar to the hash value of a file). The server then returns the value to the client using Etag. The client uses if-none-match or if-match to add the value to the next request. If they are, do not return the resource.

If-none-match differs from if-match:

  • If-None-Match: tells the server to return the status code if it is consistent304If not, resources are returned
  • If-Match: tells the server to return a status code if it is inconsistent412

How can last-Modified produce Etag?

You might think that last-Modified is enough to let the client know if the local cached copy is new enough, so why Etag? Etag was introduced in HTTP 1.1 to address several last-Modified issues:

  1. The last-Modified tag is accurate only to the second level. If a file has been Modified more than once in a second, it will not accurately mark the modification time of the file

  2. If some files are generated regularly, sometimes the contents are unchanged but last-Modified changes, making the file uncacheable

  3. The server may not obtain the correct file modification time or the time on the proxy server may be inconsistent with that on the proxy server

Etag is the unique identifier of the corresponding resource on the server side that is automatically generated by the server or generated by the developer, which can more accurately control the cache. Last-modified and ETag can be used together. The server will verify the ETag first. If the ETag is consistent, the server will continue to compare last-Modified, and finally decide whether to return 304.

Summary of HTTP caching mechanisms

The cache switch is pragma, cache-control.

The cache validations are: Expires, Last-Modified, and ETAG.

From the perspective of the process, they look like this: (Image from the Web: Browser loading HTTP cache mechanism)

  • First request:
  • Request again:

2.1.3 Viewing HTTP Cache Content

Add a breakpoint to get the sandbox, and let’s open the folder and see:

  • Access path
  • View sandbox files
  • Use the DB view tool, which I’m using hereDB Browser for SQLite
  • Select the first tablecfurl_cache_blob_dataBrowse the data.
  • Choose number oneresponse_objectExport binary on the rightbinFile using terminal directcatCommand to view the file.

As you can see, the HTTP request information is stored in this database table.

If you look further, you will see that all the information is stored in the various tables.

2.1.4 Using Other Cache Policies

Again, in the previous case, let’s modify it slightly:

- (void)example_1{
    NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/50x50.jpg"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
    // The HTTP cache policy is used by default
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; \
    // Compare services to check whether resources are updated
    if (self.lastModified) {
        [request setValue:self.lastModified forHTTPHeaderField:@"If-Modified-Since"];
    }
// if (self.etag) {
// [request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
/ /}
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"error warning : %@",error);
        }else{
            // Read data from cache!
            NSData *tempData = data;
            NSString *responseStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
            self.lastModified = [(NSHTTPURLResponse *)response allHeaderFields][@"Last-Modified"];
// self.etag = [(NSHTTPURLResponse *)response allHeaderFields][@"Etag"];
            NSLog(@"response:%@",response);
        }
    }] resume];
    
}

- (IBAction)reloadDataAction:(id)sender {
    [self example_1];
}
Copy the code

Run, load for the first time, return 200, hit Reload to load again. Print the following:

lastModified
etag

3. NSURLCache and its own cache mechanism in SDWebImage

Open the SDWebImage source code directly. Go to the following method of sdWebImagedownloader. m.

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;

    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        __strong __typeof (wself) sself = wself;
        NSTimeInterval timeoutInterval = sself.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
        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];
    /*... * /
}
Copy the code

It contains this remarkable passage:

NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;

That is, SD will ignore NSURLCache by default unless options is specified as UseNSURLCache.

The purpose of this is to prevent multiple cache, avoid SD implementation of the cache and NSURLCache multiple cache caused by resource waste.

Similarly, in SDWebImageDownloaderOperation. J m, NSURLSessionDataDelegate willCacheResponse agent method Can also see:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void(^) (NSCachedURLResponse *cachedResponse))completionHandler {
    
    NSCachedURLResponse *cachedResponse = proposedResponse;

    if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
        // Prevents caching of responses
        cachedResponse = nil;
    }
    if(completionHandler) { completionHandler(cachedResponse); }}Copy the code

When set NSURLRequestReloadIgnoringLocalCacheData strategies, ignore NSURLCache cache.

3.1 Download option in SDWebImage

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    SDWebImageDownloaderProgressiveDownload = 1 << 1,

    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,

    SDWebImageDownloaderContinueInBackground = 1 << 4,

    SDWebImageDownloaderHandleCookies = 1 << 5,

    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    SDWebImageDownloaderHighPriority = 1 << 7,

    SDWebImageDownloaderScaleDownLargeImages = 1 << 8};Copy the code

That we also to search SDWebImageDownloaderIgnoreCachedResponse the options, SD did what processing.

  • First search result:SDWebImageDownloaderOperation.mstartThe method includes this passage:
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
    // Grab the cached data for later check
    NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
    if (cachedResponse) {
        self.cachedData = cachedResponse.data; }}Copy the code

Load the disk cache data of NSURLCache for the following purposes.

  • Second search result:SDWebImageDownloaderOperation.mdidCompleteWithErrorThere is a paragraph in the proxy method:
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
    // call completion block with nil
    [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
}
Copy the code

Here directly returned to nil, i.e. SDWebImageDownloaderIgnoreCachedResponse mechanism of the options is when the image is from NSURLCache access to, it returns nil.

So that’s NSURLCache, SDWebImage and HTTP cache strategy analysis. If there are any mistakes, please correct them.