Подтвердить что ты не робот

Как кэшировать с использованием NSURLSession и NSURLCache. Не работает

У меня есть тестовое приложение, и он успешно загружает контент из сети, даже если пользователь переключает приложения во время загрузки. Отлично, теперь у меня есть фоновые загрузки на месте. Теперь я хочу добавить кеширование. Нет смысла скачивать изображения более одного раза, 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

Теперь, когда я иду, чтобы загрузить элемент в 2-й раз, опыт является exaccty, как первый! Занимает много времени, чтобы загружать и показывать прогресс. Анимированная анимация медленная и стабильная, как оригинальная загрузка. Я хочу, чтобы данные в кеше немедленно! Что мне не хватает???

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

Хорошо, спасибо 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 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?
}

Что вы делаете над моими изменениями выше? Почему я не вижу, чтобы файл возвращался очень быстро при последующих запросах?

Как я могу подтвердить, будет ли файл подан из кеша во втором запросе?

4b9b3361

Ответ 1

Обратите внимание, что следующая 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 worth it!
}

В дополнение к строке sleep(1) также обратите внимание на размер моего кеша; 500MB. Вам нужен размер кеша, который больше, чем то, что вы пытаетесь кэшировать. Так, например, если вы хотите иметь возможность кэшировать 10 Мбайт изображения, то размер кэша размером 10 МБ или даже 20 МБ будет недостаточным. Если вам нужно кэшировать 10 МБ, я бы рекомендовал кэш 100 МБ. Надеюсь это поможет!! Сделал трюк для меня.

Ответ 2

Решение - сначала получите всю информацию и нужно что-то вроде этого

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

Ответ 3

Как только вы установите кеш и сеанс, вы должны использовать сеансовые методы для загрузки ваших данных:

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

После первого вызова изображение кэшируется.