NSURLProtocol
NSURLProtocol
是 iOS里面的URL Loading System的一部分,URL loading system 原生已经支持了 http,https,file,ftp,data
这些常见协议,当然也允许我们定义自己的 protocol
去扩展,或者定义自己的协议。当URL loading system通过 NSURLRequest
对象进行请求时,将会自动创建 NSURLProtocol
(是一个对象)的实例(可以是自定义的),这样我们就有机会对该请求进行处理。
- 可以拦截
UIWebView
和WKWebView
(需额外处理),基于系统的NSURLConnection
或者NSURLSession
进行封装的网络请求。 - 忽略网络请求,直接返回自定义的
Response
- 修改
request
(请求地址,认证信息等等) - 返回数据拦截
对URL Loading System不清楚的,可以看看下面这张图,看看里面有哪些类:
使用NSURLProtocol的主要可以分为5个步骤:
注册—>拦截—>转发—>回调—>结束
NSURLProtocol的创建
@interface CFTHTTPProtocol : NSURLProtocol
@end
- 首先是继承系统的
NSURLProtocol
:
[NSURLProtocol registerClass:[CFTHTTPProtocol class]];
- 然后在
application:didFinishLaunchingWithOptions:
方法中注册,一旦注册完毕后,它就有机会来处理所有交付给URL Loading system的网络请求。
子类NSURLProtocol必须实现的方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
- 拦截,这个方法是自定义
protocol
的入口,如果不打算处理,返回NO,如果你需要对自己关注的请求进行处理则返回YES,这样,URL loading system将会把本次请求的操作都给了你这个protocol
。
这里有个需要注意的地方,想象一下,当你去加载一个URL资源的时候,URL Loading System会询问CustomURLProtocol
是否能处理该请求,你返回YES,然后URL Loading System会创建一个CustomURLProtocol
实例然后调用NSURLSession
去获取数据,然而这也会调用URL Loading System,而你在+canInitWithRequest:
中又总是返回YES,这样URL Loading System又会创建一个CustomURLProtocol
实例导致无限循环。我们应该保证每个request只被处理一次,可以通过+setProperty:forKey:inRequest:
标示那些已经处理过的request,然后在+canInitWithRequest:
中查询该request是否已经处理过了,如果是则返回NO。
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
- 这个方法主要是用来返回格式化好的
request
,如果自己没有特殊需求的话,直接返回当前的request
就好了。如果你想做些其他的,比如地址重定向,或者请求头的重新设置,你可以copy下这个request
然后进行设置并返回一个新的request
,这是一个抽象方法,子类必须实现。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- 这个方法用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。
- (void)startLoading;
- (void)stopLoading;
- 这两个方法主要是开始和取消相应的request,而且需要标示那些已经处理过的request。
实现NSURLSessionDataDelegate和NSURLSessionTaskDelegate
回调,如果你对你关注的请求进行了拦截,那么你就需要通过实现 NSURLProtocolClient
这个协议的对象将消息转给URL loading system,也就是 NSURLProtocol
中的 client
这个对象。看看这个 NSURLProtocolClient
里面的方法:
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
你会发现和 NSURLSessionDelegate
很像,其实就是做了个转发的操作。