I'm registering an implementation of NSURLProtocol to do some custom handling of certain URL requests (I tag these requests by setting properties on them). These URL requests are coming from a UIWebView's load method.
- Create a UI web view (first load after launch)
- Load content
- Destroy web view
- Create a new web view (subsequent loads)
- Load content
I'm seeing significantly different behavior between steps 2 and 5. My NSURLProtocol implementation manages cached data and is designed to handle the requests. If I don't detect my property in a request, I do a second check for a specific URL (for debugging). In either case canInitWithRequest
will return YES
:
First load after launch:
2014-10-15 11:37:11.403 MYApp[5813:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x15ebbb40> https://example.com/
2014-10-15 11:37:11.404 MYApp[5813:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x15dc8da0> https://example.com/
2014-10-15 11:37:11.409 MYApp[5813:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x15ee5ef0> https://example.com/
2014-10-15 11:37:11.409 MYApp[5813:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x15ee6240> https://example.com/
2014-10-15 11:37:11.410 MYApp[5813:60b] MYURLProtocol initWithRequest:cachedResponse:client: Request: https://example.com/
2014-10-15 11:37:11.411 MYApp[5813:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x15ee69d0> https://example.com/
2014-10-15 11:37:11.415 MYApp[5813:9c07] MYURLProtocol startLoading Loading <0x15ee6240> https://example.com/
... A bunch of loading of assets occurs (cached responses) ...
2014-10-15 11:37:12.497 MYApp[5813:60b] MyWebViewController webViewDidFinishLoad: Finished loading
Others have pointed out that there are multiple calls to the protocol about the same asset, and this is not a concern, though it's interesting to note that each time it is called with a new object, and it is the 4th (of 4) object that gets passed to startLoading
. Still, no real concerns here.
Subsequent loads:
2014-10-15 11:11:27.466 MYApp[5782:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x1727c310> https://example.com/
2014-10-15 11:11:27.467 MYApp[5782:60b] MYURLProtocol canInitWithRequest: Matched property in request: <0x145b1d90> https://example.com/
2014-10-15 11:11:27.488 MYApp[5782:560f] MYURLProtocol canInitWithRequest: Matched URL in request: <0x17266060> https://example.com/
2014-10-15 11:11:27.669 MYApp[5782:60b] MYWebViewController webViewDidFinishLoad: Finished loading
This is where the behavior, in my view, is unexpected. It appears that property has been stripped off of the request by the third time it is passed to canInitWithRequest
, and then, even though we respond YES
we never actually get inited -- the page is simply returned to the UIWebView
in its entirety, with no subsequent requests for assets. Here is what the request looks like when it is created:
NSURLRequest *request = [NSURLRequest requestWithURL:myURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[self.webView loadRequest:request];
Why, when I say that my protocol can handle the request, is it not being given the opportunity to do so? My guess is that the answer is in the implementation of UIWebView itself. Any thoughts on how to work around this if I really want my protocol to be the responsible entity for loading?
*** Way to clear the cache of webview **********
Follow this code:
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
[[NSUserDefaults standardUserDefaults] synchronize];
I was able to workaround this issue by cache-busting the UIWebView cache, while not busting the NSURLCache.
- Add a unique param to the query params of the original request. I chose 'key=000000' where the value is zero-led six digit random number.
- In the protocol, strip the key in
canonicalRequestForRequest:
and ininitWithRequest:cachedResponse:client
My stripping code looks like this (there might be a cleaner way to strip the param, but this works):
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
NSURLRequest *canonicalRequest = request;
BOOL myProtocolRequest = [[NSURLProtocol propertyForKey:kMYProtocolRequest inRequest:request] boolValue];
if (myProtocolRequest)
{
NSMutableURLRequest *mutableRequest = [request mutableCopyWorkaround];
NSString *originalURLString = mutableRequest.URL.absoluteString;
NSString *regexString = [NSString stringWithFormat:@"(?:[?&])(key=[[:digit:]]{%d}&*)", kMYKeyLength];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:0 error:0];
NSTextCheckingResult *result = [regex firstMatchInString:originalURLString options:0 range:NSMakeRange(0, originalURLString.length)];
if (result.numberOfRanges > 1)
{
NSRange keyRange = [result rangeAtIndex:1];
NSLog(@"Removing '%@' from request", [originalURLString substringWithRange:keyRange]);
NSString *replacementURLString = [originalURLString stringByReplacingCharactersInRange:keyRange withString:@""];
mutableRequest.URL = [NSURL URLWithString:replacementURLString];
canonicalRequest = mutableRequest;
}
}
return canonicalRequest;
}
My init code looks like this:
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
self = [super initWithRequest:[MYURLProtocol canonicalRequestForRequest:request] cachedResponse:cachedResponse client:client];
return self;
}
I don't like that I have to do this, but I'm finally getting exactly the behavior I want. Hopefully it helps someone out there.
来源:https://stackoverflow.com/questions/26390036/nsurlprotocol-isnt-asked-to-load-after-yes-response-to-caninitwithrequest