【问题标题】:How to prevent the browser cache interfering with NSURLCache?如何防止浏览器缓存干扰 NSURLCache?
【发布时间】:2013-09-10 12:49:25
【问题描述】:

我正在尝试在 iOS 上实现以下目标:

  • 对于静态资源(.html、.js 等),始终将本地文件加载到 UIWebView
  • 允许更新协议,以便在一段时间后我们可以为相同的 URL 返回一组不同的静态资产

Download link for minimal example.

我有这个工作,但似乎NSURLCache 有时会被完全遗漏(可重现),解决此问题的唯一可靠方法是在正在加载的页面上使用讨厌的缓存破坏技巧。另一个同样令人讨厌的黑客攻击是销毁UIWebView 并创建另一个。

作为一个例子,我们有三个版本的 webapp(红色、蓝色和绿色分别代表 v1、v2 和 v3):

每个屏幕都由一个 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 的缓存行为。不幸的是,我没有看到在NSURLCache 中设置Cache-Control 标头的选项 - 我已经尝试返回带有标头设置的NSHTTPURLResponse,但这似乎被忽略了。

【问题讨论】:

    标签: ios objective-c caching uiwebview nsurlcache


    【解决方案1】:

    我不确定我的理解是否正确,但缓存控制必须在服务器端设置,例如无缓存、过期等,而不是在 iOS 端。

    其次,通过修改查询字符串即www.mysite.com/page?id=whatever...,iOS和任何浏览器都认为请求不一样,如果你用一些db编辑器打开缓存本身,你应该看到一个请求,即一个数据库条目,用于每个更改的查询。

    这种添加随机查询字符串的技巧对于避免浏览器缓存 javascript 文件非常有用。

    希望我能正确理解您的问题。

    【讨论】:

    • 在这种情况下没有“服务器端”,因为网站根本不存在。在这种情况下如何返回 cache-control 标头?
    • 只是猜测,您是否尝试在 html 中添加元标记 pragma no-cache ?
    【解决方案2】:

    浏览器似乎在网络层的缓存(即 NSURLCache)之上有自己的“缓存”。

    不确定设置缓存控制标头是否可以解决问题,但如果您想尝试,可以在 cachedResponseForRequest 的代码中这样做。在创建 NSURLResponse 对象的地方,您可以使用 initWithURL:statusCode:HTTPVersion:headerFields: 来设置标头字段(包括缓存控制标头)。在您的缓存控制标头中,您可以尝试同时使用 no-store 和 no-cache(例如 http://blog.55minutes.com/2011/10/how-to-defeat-the-browser-back-button-cache/ )。如果您尝试这样做,请告诉我们它是否有效。

    但是,在实践中,我认为最实用和可维护的解决方案是在 URL 上使用数字(即缓存清除)。顺便说一句,您可以使用一个简单的增量而不是随机数,该增量会在分配 web 视图时重置。

    【讨论】:

    • 我尝试返回一个使用标头字段初始化的 NSHTTPURLResponse 对象,但是当我查看 Safari Web Inspector 时,这些似乎都没有被拾取。
    猜你喜欢
    • 2015-07-11
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 2012-10-07
    • 2011-04-28
    • 2017-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多