NSURLProtocol之网络拦截

Qinz
NSURLProtocol是一个抽象类,需要子类去实例化,在使用的时候注册该子类,则可在自定义的 NSURLProtocol 中拦截所有的请求,进行广告过滤或重定向等操作,下面将以拦截百度为例分析该过程及使用方法。
1. 首先我们需要创建一个NSURLProtocol的子类,在使用的时候进行注册:
   [NSURLProtocol registerClass:[QURLProtocol class]];
  • 1.1 这里注意要释放
- (void)dealloc{
    [NSURLProtocol unregisterClass:[KCURLProtocol class]];
}
2. 接下来在子类中重写必须实现的5个方法:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- (void)startLoading;
- (void)stopLoading;
3. 我们在控制器中加载一个百度的网页
  NSURLRequest *request = [NSURLRequest requestWithURL: 
  [NSURL URLWithString:@"https://www.baidu.com/"]];
  [self.webView loadRequest:request];
4. 在canInitWithRequest方法中拦截百度网址,代码如下:
+(BOOL)canInitWithRequest:(NSURLRequest *)request{
    //已经拦截过的就不再k拦截,避免死循环
    if ([NSURLProtocol propertyForKey:QZProtocolKey inRequest:request]) {
        return NO;
    }
   //拦截百度,这里可以使用isEqualToString进行精准拦截
    if ([[request.URL absoluteString] containsString:@"www.baidu.com"]) {

        return YES;
    }
    return NO;
}
5. 接下来在startLoading对拦截的地址进行重定向,代码如下:
- (void)startLoading{
    
    //标记,下次不拦截自己设置的
    [NSURLProtocol setProperty:@(YES) forKey:QZProtocolKey inRequest:[self.request mutableCopy]];
 
    //重定向
    if ([[self.request.URL absoluteString] isEqualToString:@"https://www.baidu.com/"]) {
      
        NSString*url = @"https://www.jianshu.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方法中对任务进行取消:
- (void)stopLoading{
    
     [self.task cancel];
}
7. 在NSURLSessionDataDelegate中对重定向的数据进行处理:
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    
    if ([self.request.URL.absoluteString isEqualToString:@"https://www.baidu.com/"]) {
        // 将接收到的数据返回给系统处理
        [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. 这里简单演示下广告过滤,因为广告一般为图片,所以我们拦截相关类型的图片,然后替换为自己的数据,代码如下:
+(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已经被替换,当然这里只是简单演示给出思路,具体思想还有很多细节要处理。


    image.png
9. 上面还有两个方法,没特殊需求重写父类即可:
//返回规范的request
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{   
    return request;
}
/**
这个方法主要用来判断两个请求是否是同一个请求,如果是,则可以使用缓存数据,通常只需要调用父类的实现即可,默认为YES
 */
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
    return [super requestIsCacheEquivalent:a toRequest:b];
}
10. 当我们对Session进行拦截时会发现不成功(如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. 交换系统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]];
    //如果还有其他的监控protocol,也可以在这里加进去
}
注意:当我们在startLoading进行拦截处理时,要做好对应的逻辑判断,否则会引发崩溃!

以上就是对NSURLProtocol拦截网络详细分析,当然NSURLProtocol还可以做很多事情,如增加公共请求头,对API进行一些访问的统计等。最后附上Demo下载,如果帮助到你请给一个Star!

我是Qinz,希望我的文章对你有帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270