2

I've written an NSURLProtocol that will check outbound http requests against a plist of URL to local path mappings and serve up the local content instead, and then cache it using NSURLCache:

- (void)startLoading
{   
    //Could this be why my responses never come out of the cache?
    NSURLResponse *response =[[NSURLResponse alloc]initWithURL:self.request.URL
                                                      MIMEType:nil expectedContentLength:-1
                                              textEncodingName:nil];

    //Get the locally stored data for this request
    NSData* data = [[ELALocalPathSubstitutionService singleton] getLocallyStoredDataForRequest:self.request];

    //Tell the connection to cache the response
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];

    //Have the connection load the data we just fetched
    [[self client] URLProtocol:self didLoadData:data];

    //Tell the connection to finish up
    [[self client] URLProtocolDidFinishLoading:self];
}

I limit the number of times local data can be fetched to one. The intent that the first time that its fetched it'll come from the NSBundle, but thereafter it'll use the stock NSURLCache to check whether it should come from either the cache or the network:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    //Check if we have pre-loaded data for that request
    ELAPathSubstitution* pathSub = [[ELALocalPathSubstitutionService singleton] pathSubForRequest:request];

    //We don't have a mapping for this URL
    if (!pathSub)
        return NO;

    //If it's been fetched too many times, don't handle it
    if ([pathSub.timesLocalDataFetched intValue] > 0)
    {
        //Record that we refused it.
        [pathSub addHistoryItem:ELAPathSubstitutionHistoryRefusedByProtocol];
        return NO;
    }

    //Record that we handled it.
    [pathSub addHistoryItem:ELAPathSubstitutionHistoryHandledByProtocol];
    return YES;
}

Sadly, it seems as though the local data will go into the cache, but it won't ever come back out. Here's a log snippet:

History of [https://example.com/image.png]:
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryHandledByProtocol]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryHandledByProtocol]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryHandledByProtocol]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryCacheMiss]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryDataFetched]
[2014-04-29 18:01:53 +0000] = [ELAPathSubstitutionHistoryAddedToCache]
[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryRefusedByProtocol]
[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryRefusedByProtocol]

[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryCacheMiss] 
[2014-04-29 18:02:11 +0000] = [ELAPathSubstitutionHistoryAddedToCache]
[2014-04-29 18:02:50 +0000] = [ELAPathSubstitutionHistoryRefusedByProtocol]
[2014-04-29 18:02:50 +0000] = [ELAPathSubstitutionHistoryCacheHit]

My expectation is that after the first time it's refused by the protocol it'll result in a couple of cache hits but instead it always counts it as a miss, fetches the content from the server, and then after that I start getting cache hits.

My fear is that my NSURLProtocol subclass constructs its responses in a way that allows them to be cached, but prevents them from ever being pulled out of the cache. Any ideas?

Thanks in advance. :)

4

3 回答 3

6

与 URL 加载系统的缓存交互NSURLProtocolClient是作为NSURLProtocol. 如果请求被NSURLRequestUseProtocolCachePolicy用作缓存策略,则由协议实现应用正确的特定于协议的规则来确定是否应该缓存响应。

协议实现在任何适合协议的点上调用URLProtocol:cachedResponseIsValid:它的客户端,表明缓存的响应是有效的。然后客户端应该与 URL 加载系统的缓存层进行交互。

但是,由于系统提供给我们的客户端是私有且不透明的,您可能希望自己处理事务并与协议中的系统缓存进行交互。如果你想走这条路,你可以直接使用 NSURLCache 。第一步是-cachedResponse在您的协议中覆盖。如果您仔细阅读文档,默认实现仅根据传递给初始化程序的值设置它。覆盖它,使其访问共享 URL 缓存(或您自己的私有 URL 缓存):

- (NSCachedURLResponse *) cachedResponse {
    return [[NSURLCache sharedURLCache] cachedResponseForRequest:[self request]];
}

现在,在您通常调用cachedResponseIsValid:客户端的地方,也将一个存储NSCachedURLResponseNSURLCache. 例如,当您有完整的字节集和响应时:

[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:[self request]];
于 2014-05-07T04:31:20.080 回答
0

感谢您提供信息,但是,像 Scott 一样,我仍然看到缓存问题,我不清楚具体责任在哪里。

我确定来自我的自定义 NSURLProtocol 的响应正在被缓存。我像这样通知协议客户端 [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];,之后我可以手动查询 NSURLCache 并返回适当的 NSCachedURLResponse。

在后续请求中,缓存中有一个我不清楚的请求。我假设对于之前提供给我一个 etag 的请求,NSURLProtocol 客户端会在进入我的协议的请求上设置 if-none-match 标头?

我真的必须在我的自定义协议中实现自己的 http 缓存吗?

于 2015-10-05T09:14:17.327 回答
-1

我已经通过使用自定义 NSUrlCache 解决了这个问题。我认为它比 NSUrlProtocol 更容易使用。我已经在应用商店中的应用中使用了代码。代码发布在 github 上https://github.com/evermeer/EVURLCache

于 2014-05-03T10:35:58.160 回答