NSURLProtocol是一個抽象類捧颅,需要子類去實例化,在使用的時候注冊該子類较雕,則可在自定義的 NSURLProtocol 中攔截所有的請求碉哑,進行廣告過濾或重定向等操作,下面將以攔截百度為例分析該過程及使用方法亮蒋。
1. 首先我們需要創(chuàng)建一個NSURLProtocol的子類扣典,在使用的時候進行注冊:
[NSURLProtocol registerClass:[QURLProtocol class]];
- 1.1 這里注意要釋放
- (void)dealloc{
[NSURLProtocol unregisterClass:[KCURLProtocol class]];
}
2. 接下來在子類中重寫必須實現(xiàn)的5個方法:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- (void)startLoading;
- (void)stopLoading;
3. 我們在控制器中加載一個百度的網(wǎng)頁
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:@"https://www.baidu.com/"]];
[self.webView loadRequest:request];
4. 在canInitWithRequest方法中攔截百度網(wǎng)址,代碼如下:
+(BOOL)canInitWithRequest:(NSURLRequest *)request{
//已經(jīng)攔截過的就不再k攔截,避免死循環(huán)
if ([NSURLProtocol propertyForKey:QZProtocolKey inRequest:request]) {
return NO;
}
//攔截百度,這里可以使用isEqualToString進行精準(zhǔn)攔截
if ([[request.URL absoluteString] containsString:@"www.baidu.com"]) {
return YES;
}
return NO;
}
5. 接下來在startLoading對攔截的地址進行重定向慎玖,代碼如下:
- (void)startLoading{
//標(biāo)記贮尖,下次不攔截自己設(shè)置的
[NSURLProtocol setProperty:@(YES) forKey:QZProtocolKey inRequest:[self.request mutableCopy]];
//重定向
if ([[self.request.URL absoluteString] isEqualToString:@"https://www.baidu.com/"]) {
NSString*url = @"http://www.reibang.com/";
NSURLRequest*myRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration defaultSessionConfiguration];
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
self.queue.name = @"com.Qinz.cn";
NSURLSession *session =
[NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:self.queue];
//偷梁換柱
self.task = [session dataTaskWithRequest:myRequest];
[self.task resume];
}
}
6. 記得在stopLoading方法中對任務(wù)進行取消:
- (void)stopLoading{
[self.task cancel];
}
7. 在NSURLSessionDataDelegate中對重定向的數(shù)據(jù)進行處理:
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
if ([self.request.URL.absoluteString isEqualToString:@"https://www.baidu.com/"]) {
// 將接收到的數(shù)據(jù)返回給系統(tǒng)處理
[self.client URLProtocol:self didLoadData:data];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
if (response != nil){
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
}
}
8. 這里簡單演示下廣告過濾,因為廣告一般為圖片趁怔,所以我們攔截相關(guān)類型的圖片湿硝,然后替換為自己的數(shù)據(jù)薪前,代碼如下:
+(BOOL)canInitWithRequest:(NSURLRequest *)request{
//Hook圖片,用于廣告過濾等
NSArray *array = @[@"png", @"jpeg", @"gif", @"jpg"];
if([array containsObject:request.URL.pathExtension]){
return YES;
}
return NO;
}
- (void)startLoading{
//過濾廣告
NSArray *array = @[@"png",@"jpg",@"jpeg"];
if ([array containsObject:[self.request.URL pathExtension]]) {
NSData *data = [self getImageData];
[self.client URLProtocol:self didLoadData:data];
}
}
-
8.1 百度的logo已經(jīng)被替換关斜,當(dāng)然這里只是簡單演示給出思路示括,具體思想還有很多細(xì)節(jié)要處理。
9. 上面還有兩個方法痢畜,沒特殊需求重寫父類即可:
//返回規(guī)范的request
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
return request;
}
/**
這個方法主要用來判斷兩個請求是否是同一個請求垛膝,如果是,則可以使用緩存數(shù)據(jù)丁稀,通常只需要調(diào)用父類的實現(xiàn)即可,默認(rèn)為YES
*/
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
10. 當(dāng)我們對Session進行攔截時會發(fā)現(xiàn)不成功(如AF)吼拥,這里需要進行特殊處理:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSString *url = @"http://www.baidu.com";
[manager GET:url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"AFN---%@",responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"AFN---%@",error);
}];
11. 交換系統(tǒng)Session配置方法,返回我們自己的子類即可:
#pragma mark - hook
+ (void)hookNSURLSessionConfiguration{
Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");
Method originalMethod = class_getInstanceMethod(cls, @selector(protocolClasses));
Method stubMethod = class_getInstanceMethod([self class], @selector(protocolClasses));
if (!originalMethod || !stubMethod) {
[NSException raise:NSInternalInconsistencyException format:@"沒有這個方法 無法交換"];
}
method_exchangeImplementations(originalMethod, stubMethod);
}
- (NSArray *)protocolClasses {
return @[[QURLProtocol class]];
//如果還有其他的監(jiān)控protocol,也可以在這里加進去
}
注意:當(dāng)我們在startLoading進行攔截處理時线衫,要做好對應(yīng)的邏輯判斷凿可,否則會引發(fā)崩潰!
以上就是對NSURLProtocol攔截網(wǎng)絡(luò)詳細(xì)分析授账,當(dāng)然NSURLProtocol還可以做很多事情矿酵,如增加公共請求頭,對API進行一些訪問的統(tǒng)計等矗积。最后附上Demo下載全肮,如果幫助到你請給一個Star!
我是Qinz,希望我的文章對你有幫助棘捣。