使用NSURLProtocol对UIWebView做网络拦截
在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。
NSURLProtocol无法直接使用,可以使用自定义的URLProtocol子类来做网络拦截。
//拦截前需要先调用注册:
[NSURLProtocol registerClass:[WSURLProtocol class]];
@interface WSURLProtocol : NSURLProtocol<NSURLSessionDataDelegate>
@end
@implementation WSURLProtocol
/**
return YES表示需要经过改protocol的处理
return NO 表示不经过该protocol处理,一切如旧
通过该方法还可以对request进行进一步的处理或者重定向等等
*/
/**
return YES表示需要经过改protocol的处理
return NO 表示不经过该protocol处理,一切如旧
通过该方法还可以对request进行进一步的处理或者重定向等等
*/
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([NSURLProtocol propertyForKey:@"propertyKey" inRequest:request]) {
return NO;
}
// 拦截百度的logo
if ([[request.URL absoluteString] isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo_web.png"]) {
//拦截完会调用canonicalRequestForRequest,获取一个标准的request对象,然后执行initWithRequest:cachedResponse:client:方法进行实例化,接下来就开始进入了发送网络请求,获取数据并返回的阶段了:startLoading
return YES;
}
return NO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
NSLog(@"%s",__func__);
return request;
}
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {
return [super initWithRequest:request cachedResponse:cachedResponse client:client];
}
- (void)startLoading {
// 拦截并返回自己的数据
if ([[self.request.URL absoluteString] isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo_web.png"]) {
NSData *data = [self getImageData];
[self.client URLProtocol:self didLoadData:data];
}
// 或者重定向 此时请求www.baidu.com/就会被重定向去执行这个request
if ([[self.request.URL absoluteString] isEqualToString:@"https://www.baidu.com/"]) {
NSString *url = @"http://127.0.0.1:8080/getMethod/";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
//设置一个标记位,不让这个request再去执行canInitWithRequest
[NSURLProtocol setProperty:@(YES) forKey:@"propertyKey" inRequest:request];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
NSLog(@"Connection - %@---%@",response,data);
}];
}
}
#pragma mark - private
- (NSData *)getImageData{
NSString *fileName = [[NSBundle mainBundle] pathForResource:@"lufei.jpg" ofType:@""];
return [NSData dataWithContentsOfFile:fileName];
}
@end
注意:该方法不能拦截WKWebView,因为WKWebView走的是WebKit内核处理,不是URLSession这一套
NSURLProtocol不仅可以拦截request,也可以拦截整个网络请求,比如NSURLConnection、NSURLSession、AFNetworking。但是这里有一个坑,NSURLConnection是可以正常拦截的,如上一样。但是NSURLSession和AFNetworking并不能完美的进行拦截(请求是正常发送出去了,只是返回的数据是我们自己的,相当于打开了旧的链接也打开了新的链接),我们的目的是请求时也要做拦截。那么这是为什么呢,我们来拦截一个URLSession请求,并po出他的ProtocolClasses:

发现根本没有我们自定义的Protocol,AFNetworking是基于Session的封装,也是同理,那么怎么解决这个问题呢?
我们可以通过runtime来重新给session的protocolClasses来赋值:
+ (void)hookNSURLSessionConfiguration{
Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");
Method originalMethod = class_getInstanceMethod(cls, @selector(protocolClasses));
Method stubMethod = class_getInstanceMethod([self class], @selector(selfProtolClasses));
if (!originalMethod || !stubMethod) {
[NSException raise:NSInternalInconsistencyException format:@"没有这个方法 无法交换"];
}
method_exchangeImplementations(originalMethod, stubMethod);
}
- (NSArray *)selfProtolClasses {
return @[[WSURLProtocol class]];
/**
如果还有其他的监控protocol,也可以在这里加进去,为了不影响原有功能,
<__NSArrayI 0x60000057c9c0>(
_NSURLHTTPProtocol,
_NSURLDataProtocol,
_NSURLFTPProtocol,
_NSURLFileProtocol,
NSAboutURLProtocol
)
原有的这几个也最好加进去
*/
}
网友评论