NSURLProtocol
NSURLProtocol能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。
使用场景
不管你是通过UIWebView, NSURLConnection 或者第三方库 (AFNetworking, MKNetworkKit等),他们都是基于NSURLConnection或者 NSURLSession实现的,因此你可以通过NSURLProtocol做自定义的操作。
-
重定向网络请求
忽略网络请求,使用本地缓存
自定义网络请求的返回结果
一些全局的网络请求设置
接触过iOS系统中URL Loading System都知道,NSURLProtocol是如此地强大,可以拦截应用内几乎所有的网络请求(除了WKWebView),并可以修改请求头,返回client任意自定义的数据等等,据说很多做网络缓存都是利用这个类的。
那么,首先讲解一下NSURLProtocol怎么使用吧。
1. 定义一个NSURLProtocol的子类
在继承NSURLProtocol中,我们需要实现
+ (BOOL)canInitWithRequest:(NSURLRequest *)request, 定义拦截请求的URL规则
- (void)startLoading, 对于拦截的请求,系统创建一个NSURLProtocol对象执行startLoading方法开始加载请求
- (void)stopLoading,对于拦截的请求,NSURLProtocol对象在停止加载时调用该方法
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request,可选方法,对于需要修改请求头的请求在该方法中修改
下面代码定义了一个专门拦截https请求的NSURLProtocol子类,并通过CFHttpMessageRef重新请求
@interface CFHttpMessageURLProtocol () <NSStreamDelegate> {
NSMutableURLRequest *curRequest;
NSRunLoop *curRunLoop;
NSInputStream *inputStream;
}
@end
@implementation CFHttpMessageURLProtocol
/**
* 是否拦截处理指定的请求
*
* @param request 指定的请求
*
* @return 返回YES表示要拦截处理,返回NO表示不拦截处理
*/
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
/* 防止无限循环,因为一个请求在被拦截处理过程中,也会发起一个请求,这样又会走到这里,如果不进行处理,就会造成无限循环 */
if ([NSURLProtocol propertyForKey:protocolKey inRequest:request]) {
return NO;
}
NSString *url = request.URL.absoluteString;
// 如果url以https开头,则进行拦截处理,否则不处理
if ([url hasPrefix:@"https"]) {
return YES;
}
return NO;
}
/**
* 如果需要对请求进行重定向,添加指定头部等操作,可以在该方法中进行
*/
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
/**
* 开始加载,在该方法中,加载一个请求
*/
- (void)startLoading {
NSMutableURLRequest *request = [self.request mutableCopy];
// 表示该请求已经被处理,防止无限循环
[NSURLProtocol setProperty:@(YES) forKey:protocolKey inRequest:request];
curRequest = request;
[self startRequest];
}
/**
* 取消请求
*/
- (void)stopLoading {
if (inputStream.streamStatus == NSStreamStatusOpen) {
[inputStream removeFromRunLoop:curRunLoop forMode:NSRunLoopCommonModes];
[inputStream setDelegate:nil];
[inputStream close];
}
[self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"stop loading" code:-1 userInfo:nil]];
}










