ios 如何使用 NSURLSession 和 NSURLCache 进行缓存。不工作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21957378/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 23:32:36  来源:igfitidea点击:

How to cache using NSURLSession and NSURLCache. Not working

iosiphonensurlsessionnsurlcachensurlsessionconfiguration

提问by John Erck

I have a test app setup and it successfully downloads content from the network even if the user switches apps while a download is in progress. Great, now I have background downloads in place. Now I want to add caching. There is no point to me downloading images more than once, b/c of system design, given an image URL I can tell you the content behind that URL will never change. So, now I want to cache the results of my download using apple's built in in-memory/on-disk cache that I've read so much about (as opposed to me saving the file manually in NSCachesDirectory and then checking there before making new request, ick). In an attemptto get caching working on top of this working code, I added the following code:

我有一个测试应用程序设置,即使用户在下载过程中切换应用程序,它也能成功地从网络下载内容。太好了,现在我有后台下载了。现在我想添加缓存。给我一个图像 URL,我可以告诉你该 URL 背后的内容永远不会改变,我没有必要多次下载图像,系统设计的 b/c。所以,现在我想使用苹果的内置内存/磁盘缓存来缓存我的下载结果,我已经阅读了很多(而不是我在 NSCachesDirectory 中手动保存文件,然后在创建新文件之前检查那里)请求,ick)。在尝试得到缓存在此工作的代码顶部的工作,我添加以下代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.

    // Set app-wide shared cache (first number is megabyte value)
    [NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:60 * 1024 * 1024
                                                                diskCapacity:200 * 1024 * 1024
                                                                    diskPath:nil]];

    return YES;
}

When I create my session, I've added two NEW lines (URLCache and requestCachePolicy).

创建会话时,我添加了两个新行(URLCache 和 requestCachePolicy)。

// Helper method to get a single session object
- (NSURLSession *)backgroundSession
{
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.example.apple-samplecode.SimpleBackgroundTransfer.BackgroundSession"];
        configuration.URLCache = [NSURLCache sharedURLCache]; // NEW LINE ON TOP OF OTHERWISE WORKING CODE
        configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;  // NEW LINE ON TOP OF OTHERWISE WORKING CODE
        session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    });
    return session;
}

Then, just to be ultra redundant in an attempt to see caching success I switched my NSURLRequest line from

然后,为了尝试看到缓存成功,我将我的 NSURLRequest 行从

// NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; // Old line, I've replaced this with...
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:2*60]; // New line

Now, when I go to download the item a 2nd time, the experience is exaclty like the first!! Takes a long time to download and progress bar is animated slow and steady like an original download. I want the data in the cache immediately!! What am I missing???

现在,当我第二次下载该项目时,体验和第一次一样好!!下载需要很长时间,进度条动画缓慢而稳定,就像原始下载一样。我要立即缓存中的数据!!我错过了什么???

----------------------------UPDATE----------------------------

- - - - - - - - - - - - - - 更新 - - - - - - - - - - - -------

Okay, thanks to Thorsten's answer, I've added the following two lines of code to my didFinishDownloadingToURLdelegate method:

好的,感谢 Thorsten 的回答,我在我的didFinishDownloadingToURL委托方法中添加了以下两行代码:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL {

    // Added these lines...
    NSLog(@"DiskCache: %@ of %@", @([[NSURLCache sharedURLCache] currentDiskUsage]), @([[NSURLCache sharedURLCache] diskCapacity]));
    NSLog(@"MemoryCache: %@ of %@", @([[NSURLCache sharedURLCache] currentMemoryUsage]), @([[NSURLCache sharedURLCache] memoryCapacity]));
    /*
    OUTPUTS:
    DiskCache: 4096 of 209715200
    MemoryCache: 0 of 62914560
    */
}

This is great. It confirms the cache is growing. I presume since I'm using a downloadTask (downloads to file as opposed to memory), that that's why DiskCache is growing and not memory cache first? I figured everything would go to memory cache until that overflowed and then disk cache would be used and that maybe memory cache was written to disk before the OS kills the app in the background to free up memory. Am I misunderstanding how Apple's cache works?

这很棒。它确认缓存正在增长。我认为因为我使用的是 downloadTask(下载到文件而不是内存),这就是为什么 DiskCache 正在增长而不是内存缓存首先?我认为一切都将进入内存缓存,直到溢出,然后将使用磁盘缓存,并且可能在操作系统在后台终止应用程序以释放内存之前将内存缓存写入磁盘。我误解了 Apple 的缓存是如何工作的吗?

This is a step forward for sure, but the 2nd time I download the file it takes just as long as the first time(maybe 10 seconds or so) and the following method DOES get executed again:

这肯定是向前迈出的一步,但是我第二次下载文件所需的时间与第一次一样长(可能 10 秒左右),并且以下方法确实会再次执行:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    // This shouldn't execute the second time around should it? Even if this is supposed to get executed a second time around then shouldn't it be lightning fast? It's not.
    // On all subsequent requests, it slowly iterates through the downloading of the content just as slow as the first time. No caching is apparent. What am I missing?
}

What do you make of my edits above? Why am I not seeing the file returned very quickly on subsequent requests?

你对我上面的编辑有什么看法?为什么我没有看到文件在后续请求中很快返回?

How can I confirm if the file is being served from the cache on the 2nd request?

如何确认文件是否在第二次请求时从缓存中提供?

回答by John Erck

Note that the following SO post helped me solve my problem: Is NSURLCache persistent across launches?

请注意,以下 SO 帖子帮助我解决了我的问题:NSURLCache 在启动时是否持久?

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Set app-wide shared cache (first number is megabyte value)
    NSUInteger cacheSizeMemory = 500*1024*1024; // 500 MB
    NSUInteger cacheSizeDisk = 500*1024*1024; // 500 MB
    NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"];
    [NSURLCache setSharedURLCache:sharedCache];
    sleep(1); // Critically important line, sadly, but it's worth it!
}

In addition to the sleep(1)line, also note the size of my cache; 500MB.

除了sleep(1)行,还要注意我的缓存大小;500MB。

According to docsyou need a cache size that is way bigger than what you're trying to cache.

根据文档,您需要的缓存大小比您尝试缓存的要大得多。

The response size is small enough to reasonably fit within the cache. (For example, if you provide a disk cache, the response must be no larger than about 5% of the disk cache size.)

响应大小足够小,可以合理地放入缓存中。(例如,如果您提供磁盘缓存,则响应不得超过磁盘缓存大小的 5% 左右。)

So for example if you want to be able to cache a 10MB image, then a cache size of 10MB or even 20MB will not be enough. You need 200MB. Honey's comment below is evidence that Apple is following this 5% rule. For an 8Mb he had to set his cache size to minimum 154MB.

例如,如果您希望能够缓存 10MB 的图像,那么 10MB 甚至 20MB 的缓存大小是不够的。你需要 200MB。Honey 下面的评论证明 Apple 遵循了 5% 的规则。对于 8Mb,他必须将缓存大小设置为最小 154MB。

回答by gbk

Solution - first get all info u need it something like this

解决方案 - 首先获取您需要的所有信息,例如

- (void)loadData
{
    if (!self.commonDataSource) {
        self.commonDataSource = [[NSArray alloc] init];
    }

    [self setSharedCacheForImages];

    NSURLSession *session = [self prepareSessionForRequest];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[BaseURLString stringByAppendingPathComponent:@"app.json"]]];
    [request setHTTPMethod:@"GET"];
    __weak typeof(self) weakSelf = self;
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (!error) {
            NSArray *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
            weakSelf.commonDataSource = jsonResponse;
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf updateDataSource];
            });
        }
    }];
    [dataTask resume];
}

- (void)setSharedCacheForImages
{
    NSUInteger cashSize = 250 * 1024 * 1024;
    NSUInteger cashDiskSize = 250 * 1024 * 1024;
    NSURLCache *imageCache = [[NSURLCache alloc] initWithMemoryCapacity:cashSize diskCapacity:cashDiskSize diskPath:@"someCachePath"];
    [NSURLCache setSharedURLCache:imageCache];
}

- (NSURLSession *)prepareSessionForRequest
{
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    [sessionConfiguration setHTTPAdditionalHeaders:@{@"Content-Type": @"application/json", @"Accept": @"application/json"}];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
    return session;
}

After you need to download each file - in my case - make parsing of response and download images. Also before making request you need to check if cache already have response for your request - something like this

在您需要下载每个文件后 - 在我的情况下 - 解析响应并下载图像。同样在发出请求之前,您需要检查缓存是否已经对您的请求做出响应 - 像这样

NSString *imageURL = [NSString stringWithFormat:@"%@%@", BaseURLString ,sourceDictionary[@"thumb_path"]];
NSURLSession *session = [self prepareSessionForRequest];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageURL]];
[request setHTTPMethod:@"GET"];

NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse.data) {
    UIImage *downloadedImage = [UIImage imageWithData:cachedResponse.data];
    dispatch_async(dispatch_get_main_queue(), ^{
        cell.thumbnailImageView.image = downloadedImage;
    });
} else {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (!error) {
        UIImage *downloadedImage = [UIImage imageWithData:data];
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.thumbnailImageView.image = downloadedImage;
        });
    }
}];
    [dataTask resume];
}

After that you can also check result with xCode Network Analyzer.

之后,您还可以使用 xCode 网络分析器检查结果。

Also note as mentionted by @jcaron and documented by Apple

另请注意@jcaron 提到并由 Apple 记录

NSURLSession won't attempt to cache a file larger than 5% of the cache size

NSURLSession 不会尝试缓存大于缓存大小 5% 的文件

Result something like

结果类似于

enter image description here

在此处输入图片说明

回答by Thorsten

Once you set the cache and the session, you should use the session-methods to download your data:

一旦你设置了缓存和会话,你应该使用 session-methods 来下载你的数据:

- (IBAction)btnClicked:(id)sender {
    NSString *imageUrl = @"http://placekitten.com/1000/1000";
    NSURLSessionDataTask* loadDataTask = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        UIImage *downloadedImage = [UIImage imageWithData:data];
        NSLog(@"ImageSize: %f, %f", downloadedImage.size.width, downloadedImage.size.height);
        NSLog(@"DiskCache: %i of %i", [[NSURLCache sharedURLCache] currentDiskUsage], [[NSURLCache sharedURLCache] diskCapacity]);
        NSLog(@"MemoryCache: %i of %i", [[NSURLCache sharedURLCache] currentMemoryUsage], [[NSURLCache sharedURLCache] memoryCapacity]);
    }];
    [loadDataTask resume]; //start request
}

After the first call, the image is cached.

第一次调用后,图像被缓存。