Как предотвратить кеш браузера, мешающий NSURLCache?

Я пытаюсь добиться следующего на iOS:

  • Всегда загружайте локальные файлы в UIWebView для статических активов (.html, .js и т. д.)
  • Разрешить обновление протокола, чтобы через некоторое время мы могли возвращать другой набор статических ресурсов для тех же URL-адресов.

Ссылка на скачивание для минимального примера.

У меня это работает, но кажется NSURLCache иногда полностью пропускается (воспроизводимо), и единственный надежный способ исправить это - использовать хитрые приемы очистки кэша на загружаемой странице. Еще один такой же неприятный хак - уничтожить UIWebView и создать другую.

В качестве примера этого у нас есть три версии нашего веб-приложения (красная, синяя и зеленая для v1, v2 и v3 соответственно):

v1v2v3

Каждый экран состоит из одного файла HTML и JS.

index.html
----------

<!DOCTYPE html>
<html>
<head>
</head>

<body style="background-color:#FF0000">
    <h1 id="label" style="color:#FFFFFF"></h1>
    <script src="app.js"></script>
</body>
</html>

app.js
------
var label = document.getElementById('label');
label.innerHTML = 'red';

Каждые 2 секунды происходит следующее:

  1. Мы меняем какие файлы NSURLCache вернется, чтобы заставить его возвращать разные версии (обратите внимание, мы реализуем это путем переопределения cachedResponseForRequest: скорее, чем storeCachedResponse:forRequest:)
  2. UIWebView загружает фиктивную страницу " http://www.cacheddemo.co.uk/index.html"

NSURLCache логика реализована просто как вращающаяся NSMutableArray:

@implementation ExampleCache

- (id)init
{
    self = [super initWithMemoryCapacity:8 * 1024 * 1024 diskCapacity:8 * 1024 * 1024 diskPath:@"webcache.db"];
    if(self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(swapCache:) name:@"swapCache" object:nil];
        self.cache = [@[@{@"index.html":@"index1.html", @"app.js":@"app1.js"},
                       @{@"index.html":@"index2.html", @"app.js":@"app2.js"},
                       @{@"index.html":@"index3.html", @"app.js":@"app3.js"}] mutableCopy];
    }

    return self;
}

- (void)swapCache:(NSNotification *)notification
{
    [self.cache addObject:self.cache[0]];
    [self.cache removeObjectAtIndex:0];
}

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
    NSString *file = [[[request URL] pathComponents] lastObject];
    NSString *mimeType;

    if([file hasSuffix:@".html"]) {
        mimeType = @"text/html";
    } else if([file hasSuffix:@".js"]) {
        mimeType = @"application/javascript";
    }

    if(mimeType) {
        NSString *cachedFile = self.cache[0][file];
        NSUInteger indexOfDot = [cachedFile rangeOfString:@"."].location;

        NSString *path = [[NSBundle mainBundle] pathForResource:[cachedFile substringToIndex:indexOfDot] ofType:[cachedFile substringFromIndex:indexOfDot + 1] inDirectory:@"www"];
        NSData *data = [NSData dataWithContentsOfFile:path];

        if(data.length) {
            NSLog(@"Response returned for %@", file);

            NSURLResponse *urlResponse = [[NSURLResponse alloc] initWithURL:[request URL] MIMEType:mimeType  expectedContentLength:data.length textEncodingName:nil];
            NSCachedURLResponse *response = [[NSCachedURLResponse alloc] initWithResponse:urlResponse data:data];
            return response;
        }
    }

    NSLog(@"No response for %@ - %@", file, request);
    return nil;
}

Логика контроллера представления использует GCD для перезагрузки UIWebView после задержки:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    [self.webView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
    [self.webView setDelegate:self];
    [self.view addSubview:self.webView];

    [self loadContent];
}

- (void)loadContent
{
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%d", @"http://www.cacheddemo.co.uk/index.html", arc4random()]]]];
//    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.cacheddemo.co.uk/index.html"]]];

    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [[NSNotificationCenter defaultCenter] postNotificationName:@"swapCache" object:nil];
        [self loadContent];
    });
}

Часть, которую я не могу понять здесь, состоит в том, что добавление строки запроса заставит перезагрузку страницы работать без сбоев (загружает R - G - B - R - G - ...) - это строка без комментариев:

[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%d", @"http://www.cacheddemo.co.uk/index.html", arc4random()]]]];

Как только мы избавимся от строки запроса, NSURLCache перестает попадать, кроме первого запроса, поэтому он остается на странице R:

[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.cacheddemo.co.uk/index.html"]]];

Тот факт, что строка запроса вызывает NSURLCache действовать так, как должно, указывает мне, что кеш браузера как-то мешает. В моей голове я считаю, что уровни кэширования работают так:

  1. Проверьте кеш браузера
  2. Если ничего не вернулось - проверьте NSURLCache
  3. Если пока ничего не возвращено - проверьте кеш прокси-сервера
  4. Наконец попытайтесь загрузить удаленный ресурс

Как мы можем полностью отключить кеш браузера, чтобы полностью контролировать его поведение UIWebView, К сожалению, я не вижу возможности установить Cache-Control заголовок в NSURLCache - Я уже пытался вернуть NSHTTPURLResponse с установленными заголовками, но это, кажется, игнорируется.

2 ответа

Я не уверен, что я правильно понимаю, но контроль кэша должен быть установлен на стороне сервера, с чем-то вроде no-cache, expires и так далее, а не на стороне iOS.

Во-вторых, изменив строку запроса, т.е. www.mysite.com/page?id=whatever...iOS и любой браузер считают, что запрос не совпадает. Если вы открыли кеш с помощью какого-либо редактора БД, вы должны были увидеть один запрос, то есть одну запись базы данных, для каждого измененного запроса.

Этот трюк добавления случайной строки запроса очень полезен для того, чтобы браузер не кэшировал файл javascript.

Надеюсь, я правильно понимаю ваш вопрос.

Браузер, похоже, имеет свой собственный "кеш" над кешем сетевого уровня (т. Е. NSURLCache).

Не уверен, что установка заголовков элемента управления кэшем решит эту проблему, но если вы хотите попробовать это, вы можете сделать это в своем коде для cachedResponseForRequest. Там, где вы создаете объект NSURLResponse, вы можете использовать initWithURL:statusCode:HTTPVersion:headerFields: для установки полей заголовка (включая заголовки элемента управления кэшем). В заголовке элемента управления кешем вы можете попробовать использовать как без хранилища, так и без кэша (например, http://blog.55minutes.com/2011/10/how-to-defeat-the-browser-back-button-cache/), Если вы попробуете это, пожалуйста, дайте нам знать, если это работает или не работает.

Однако на практике я думаю, что наиболее прагматичным и поддерживаемым решением будет использование числа в URL (т. Е. Уничтожение кэша). Кстати, вместо случайного числа вы можете использовать простое приращение, которое сбрасывается при выделении веб-просмотра.

Другие вопросы по тегам