26

我有一个测试应用程序设置,即使用户在下载过程中切换应用程序,它也会成功地从网络下载内容。太好了,现在我有后台下载。现在我想添加缓存。我没有必要多次下载图像,系统设计的 b/c,给定一个图像 URL,我可以告诉你该 URL 背后的内容永远不会改变。所以,现在我想使用我读过很多的苹果内置的内存/磁盘缓存来缓存我的下载结果(而不是我在 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;
}

创建会话时,我添加了两条新行(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;
}

然后,只是为了超级冗余以尝试查看缓存成功,我将 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

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

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

好的,感谢 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
    */
}

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

这肯定是向前迈进了一步,但是我第二次下载文件所需的时间与第一次一样长(可能是 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?
}

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

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

4

3 回答 3

32

请注意,以下 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!
}

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

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

响应大小足够小,可以合理地放入缓存中。(例如,如果您提供磁盘缓存,则响应必须不大于磁盘缓存大小的大约 5%。)

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

于 2014-04-10T17:05:15.503 回答
10

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

- (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;
}

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

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];
}

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

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

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

结果类似于

在此处输入图像描述

于 2015-06-30T15:48:13.980 回答
2

设置缓存和会话后,您应该使用会话方法下载数据:

- (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
}

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

于 2014-02-23T04:29:00.977 回答