We're building a browser for iOS. We decided to experiment with using a custom NSURLProtocol
subclass in order to implement our own caching scheme and perform user-agent spoofing. It does both of those things quite well...the problem is, navigating to certain sites (msn.com is the worst) will cause the entire app's UI to freeze for up to fifteen seconds. Obviously something is blocking the main thread, but it's not in our code.
This issue only appears with the combination of UIWebView
and a custom protocol. If we swap in a WKWebView
(which we can't use for various reasons) the problem disappears. Similarly, if we don't register the protocol such that it isn't ever utilized, the problem goes away.
It also doesn't seem to much matter what the protocol does; we wrote a bare-bones dummy protocol that does nothing but forward responses (bottom of post). We dropped that protocol into a bare-bones test browser that doesn't have any of our other code--same result. We also tried using someone else's (RNCachingURLProtocol
) and observed the same result. It appears that the simple combination of these two components, with certain pages, causes the freeze. I'm at a loss to attempt to resolve (or even investigate) this and would greatly appreciate any guidance or tips. Thanks!
import UIKit
private let KEY_REQUEST_HANDLED = "REQUEST_HANDLED"
final class CustomURLProtocol: NSURLProtocol {
var connection: NSURLConnection!
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
return NSURLProtocol.propertyForKey(KEY_REQUEST_HANDLED, inRequest: request) == nil
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override class func requestIsCacheEquivalent(aRequest: NSURLRequest, toRequest bRequest: NSURLRequest) -> Bool {
return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
}
override func startLoading() {
var newRequest = self.request.mutableCopy() as! NSMutableURLRequest
NSURLProtocol.setProperty(true, forKey: KEY_REQUEST_HANDLED, inRequest: newRequest)
self.connection = NSURLConnection(request: newRequest, delegate: self)
}
override func stopLoading() {
connection?.cancel()
connection = nil
}
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
self.client!.URLProtocol(self, didLoadData: data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
self.client!.URLProtocolDidFinishLoading(self)
}
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
self.client!.URLProtocol(self, didFailWithError: error)
}
}