如何防止浏览器缓存干扰NSURLCache?

时间:2013-09-10 12:49:25

标签: ios objective-c caching uiwebview nsurlcache

我正在尝试在iOS上实现以下功能:

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

Download link for minimal example

我有这个工作,但似乎NSURLCache有时完全错过(可重现),唯一可靠的解决方法是在正在加载的页面上使用令人讨厌的缓存技巧。另一个同样讨厌的黑客是摧毁UIWebView并创建另一个。

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

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
  3. 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. 最后尝试加载远程资源
    5. 我们如何完全禁用浏览器缓存,以便我们可以完全控制UIWebView的缓存行为。很遗憾,我没有看到在Cache-Control中设置NSURLCache标头的选项 - 我已经尝试返回设置了标头的NSHTTPURLResponse,但这似乎被忽略了。

2 个答案:

答案 0 :(得分:0)

我不确定我是否理解正确,但是必须在服务器端设置缓存控制,例如no-cache,expires等,而不是iOS端。

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

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

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

答案 1 :(得分:0)

浏览器确实在网络层缓存(即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上的数字(即缓存清除)。 BTW而不是随机数,您可以使用一个简单的增量,只要分配了webview,它就会被重置。