web-dev-qa-db-ja.com

NSURLSessionおよびNSURLCacheを使用してキャッシュする方法。動かない

テストアプリのセットアップがあり、ダウンロードの進行中にユーザーがアプリを切り替えても、ネットワークからコンテンツを正常にダウンロードします。素晴らしい、今はバックグラウンドでダウンロードできます。次に、キャッシュを追加します。画像のURLが与えられた場合、システム設計のb/cで、画像を複数回ダウンロードしても意味がありません。そのURLの背後にあるコンテンツは変更されません。だから、今、私はよく読んだAppleの内蔵インメモリ/オンディスクキャッシュを使用してダウンロードの結果をキャッシュしたい(NSCachesDirectoryに手動でファイルを保存し、新しいファイルを作成する前にチェックするのとは対照的に)リクエスト、ick)。 attemptで、この作業コードの上でキャッシュを機能させるために、次のコードを追加しました。

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

セッションを作成するときに、2つの新しい行(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

これで、2回目にアイテムをダウンロードするときに、最初の体験と同じように体験できます。ダウンロードに時間がかかり、進行状況バーは元のダウンロードのようにゆっくりと安定してアニメーション表示されます。データをすぐにキャッシュに入れたい!私は何が欠けていますか?

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

さて、Thorstenの回答のおかげで、次の2行のコードを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が成長しているのではなく、メモリキャッシュが先にある理由でしょうか。オーバーフローするまですべてがメモリキャッシュに移動し、ディスクキャッシュが使用され、OSがバックグラウンドでアプリを強制終了してメモリを解放する前にメモリキャッシュがディスクに書き込まれる可能性があると考えました。 Appleのキャッシュのしくみを誤解していますか?

これは確かに一歩ですが、ファイルを2回ダウンロードすると最初と同じくらい時間がかかります(おそらく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?
}

上記の私の編集をどうしますか?後続のリクエストでファイルがすぐに返されないのはなぜですか?

2番目の要求でファイルがキャッシュから提供されているかどうかを確認するにはどうすればよいですか?

26
John Erck

次の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。

docs によると、キャッシュしようとしているものよりもはるかに大きいキャッシュサイズが必要です。

応答サイズは、キャッシュ内に合理的に収まるほど小さくなっています。 (たとえば、ディスクキャッシュを提供する場合、応答はディスクキャッシュサイズの約5%以下でなければなりません。)

たとえば、10MBの画像をキャッシュしたい場合、10MBまたは20MBのキャッシュサイズでは十分ではありません。 200MBが必要です。以下のHoneyのコメントは、Appleがこの5%ルールに従っているという証拠です。 8Mbでは、キャッシュサイズを最小154MBに設定する必要がありました。

32
John Erck

解決策-最初にこのような何かが必要なすべての情報を取得する

- (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 Network Analyzerで結果を確認することもできます。

また、@ jcaronと Appleが文書化 で言及されているように注意してください

NSURLSessionは、キャッシュサイズの5%を超えるファイルをキャッシュしようとしません。

次のような結果

enter image description here

10
gbk

キャッシュとセッションを設定したら、セッションメソッドを使用してデータをダウンロードする必要があります。

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

最初の呼び出しの後、画像はキャッシュされます。

2
Thorsten