前段时间写过UIWebView 设置请求头文章,后面发现那样做是有bug的,因为当我A到B回到A再进入B界面的时候,这是B已经加载过了,B的url已经存在那个数组里面,所以不会再塞请求头了,但是这样子就会请求失败,所以这个方法是不行的。好在NSURLProtocol能解决这个问题。
An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.
NSURLProtocol 对象处理加载特定的url。它是一个为处理特定scheme url提供基础解决方案的抽象类。我们可以创建App支持的解决特定协议或者url的子类。
下面是我处理请求头的代码,在viewDidLoad
方法里面调用 registerClass:
方法,dealloc
里面调用unregisterClass:
方法
1 | static NSString *const URLProtocolHandledKey = @"URLProtocolHandledKey"; |
下面来说说它的处理流程以及相关方法。propertyForKey:inRequest:
和setProperty:forKey:inRequest:
方法是用来处理NSURLRequest
或者NSMutableURLRequest
的,可以标志该request是否已做过处理。 上面的URLProtocolHandledKey
就是用来标记该request是否处理过。并且还有一个最基本的功能,就是创建NSURLResponse来处理request请求成功的情况。
####Registering and Unregistering Protocol Classes
####registerClass:
注册一个NSURLProtocol的子类,让URL loading system
知道它的存在。当它注册失败返回NO时,说明注册的类不是 NSURLProtocol 的子类。
当URL loading system
开始加载一个request时,每一个注册的protocol都会去看自己是否能够被特定的request初始化。当第一个注册protocol,canInitWithRequest:
方法返回YES时,它就去加载特定的url了,所以这里并不能保证所有注册的protocol都能处理。protocol处理的顺序和它们注册的顺序相反,即后注册的先处理。处理模式就是在canonicalRequestForRequest:
里面创建一个权威的request去请求。
####unregisterClass:
取消注册。该方法调用后,该protocolClass不再被URL loading system
处理。
####Determining If a Subclass Can Handle a Request
#####canInitWithRequest:
该protocolClass是否处理该request。子类必须实现该方法。
####Getting and Setting Request Properties
#####propertyForKey:inRequest:
#####setProperty:forKey:inRequest:
#####removePropertyForKey:inRequest:
顾名思义,这个三个方法就是根据某个key取出、设置、移除request的属性。
####Providing a Canonical Version of a Request
#####canonicalRequestForRequest:
权威的request(个人理解就是被处理过的request)。要保证该protocolClass处理过的request要有统一的形式(一个protocolClass 你不能即添加请求头,又改变url的query,这是错误的做法)。该方法子类必须实现,在实现的过程中得考虑URL cache
缓存问题,因为the canonical form of a request
习惯于从URL cache
中查找对象用来检测两个NSURLRequest
是否相等。
####Determining If Requests Are Cache Equivalent
#####requestIsCacheEquivalent:toRequest:
该方法用来检测两个request在缓存意义上是否相等。
该方法当且仅当request用相同的protocol来处理,并且当它们执行过特定的检测后protocol证明它们两个相等。
####Starting and Stopping Downloads
#####startLoading
执行特定的request请求。 子类必须实现该方法。
该方法执行后,子类需要加载该request并且通过NSURLProtocolClient
协议来处理URL loading system
的回调。
#####stopLoading
取消特定的request请求。 子类必须实现该方法。
该方法能够取消一个正在进行中的请求,并且还要停止对该protocolClass的client
属性发通知。
####Getting Protocol Attributes
#####cachedResponse
缓存的响应数据。如果没有在子类里重载,则会返回在初始化时存储的值。
#####client
与URL loading system
交互的接受者。
####request
请求对象request。
####NSURLProtocolClientNSURLProtocolClient
为NSURLProtocol
提供于URL loading system
交互的接口。App没有必要去实现该协议。从上面看到的代码看到,在NSURLConnectionDelegate
的代理方法里面有client
响应的处理方法。
####总结
- 当然了,这里的网络请求也可以用
NSURLSession
,只要将请求返回的数据让client
与URL loading system
交互即可。 - 上面的代码处理是参考的matt大神的NSEtcHosts
- Apple Sample Code里面竟然没有
NSURLProtocol
的samplecode
####问题 NSURLProtocol不支持WKWebView ?
It’s likely that this worked with UIWebView as an accident of the implementation. WKWebView, OTOH, does all of its networking out of process, so such accidents are rare (everything that traverses the inter-process gap has to be explicitly coded to do so). fromhttps://forums.developer.apple.com/thread/18952
NSURLProtocol这种处理对
WKWebView是不起作用的,因为
WKWebView的加载是在另外一个进程里。如果现有的项目中是用
WKWebView`的,那么怎么塞请求头呢?我目前想到的只有通过特定的协议采取js交互。
search keyword:wkwebview set header、 wkwebview custom header
- WKWebViewでNSURLRequestをPOSTするとヘッダーが消える問題(解決)
- Can’t set headers on my WKWebView POST request
- How To add HttpHeader in request globally for ios swift
- WKWebView and NSURLProtocol not working)
####问题 重定向问题
最近在 load webPage 的时候,发现重定向的时候 (A->B->C) 有问题,不能 load C,只能到 B,或者到 A。现在假设 (A->B) 这个情况,通过调试发现,两个 URL 的 NSURLProtocol 方法如下:
A:
- canInitWithRequest:
- canonicalRequestForRequest:
- startLoading
B:
- canInitWithRequest:
- canonicalRequestForRequest:
也就是说 startLoading 方法没有调用,webView 就不会加载 B。后面通过查找资料发现是网络请求的原因,于是将 NSURLConnection 换成了 NSURLSession,因为 NSURLSession 有个重定向的代理方法 URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
调整后的实例代码如下(其他的代码逻辑处理还是一样的),
1 | - (void)startLoading { |
####参考链接
- https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLProtocol_Class/
- https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSURLProtocolClient_Protocol/
- https://github.com/mattt/NSEtcHosts
- http://stackoverflow.com/questions/25539837/how-to-add-customize-http-headers-in-uiwebview-request-my-uiwebview-is-based-on
####其他学习链接