前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS开发之NSURLProtocol的那些坑

iOS开发之NSURLProtocol的那些坑

作者头像
forrestlin
发布2018-05-24 11:00:34
2.4K0
发布2018-05-24 11:00:34
举报
文章被收录于专栏:蜉蝣禅修之道蜉蝣禅修之道

接触过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重新请求

代码语言:javascript
复制
@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]];
}

以上代码中的startRequest方法是通过复制原始请求头,使用CFHttpMessageRef重新发起请求的,关于这部分的代码由于跟本文章内容关系不大,这里就先不放,有兴趣的朋友可以参考我的下一篇博客。

2. 在网络请求前注册NSURLProtocol

代码语言:javascript
复制
// 注册拦截请求的NSURLProtocol
[NSURLProtocol registerClass:[CFHttpMessageURLProtocol class]];

对于NSURLSession的请求,注册NSURLProtocol的方式稍有不同,是通过NSURLSessionConfiguration注册的

代码语言:javascript
复制
// NSURLSession例子
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSArray *protocolArray = @[ [CFHttpMessageURLProtocol class] ];
configuration.protocolClasses = protocolArray;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionTask *task = [session dataTaskWithRequest:_request];
[task resume];

3. 请求结束后注销NSURLProtocol

代码语言:javascript
复制
[NSURLProtocol unregisterClass:[CFHttpMessageURLProtocol class]];

好了,到这里NSURLProtocol的使用方法大家应该有所了解了。下面主要讲一下NSURLProtocol在使用过程中可能会遇到的坑,给自己以及需要的朋友留个提醒。

1. 上面一开始就已经说了,对于WebView的请求,目前NSURLProtocol还不能拦截WKWebView的请求,只能拦截UIWebview的,但后者好像AppStore已经不让审核通过了(尴尬脸)。

2. NSURLProtocol在拦截NSURLSession的POST请求时不能获取到Request中的HTTPBody,这个貌似早就国外的论坛上传开了,但国内好像还鲜有人知,据苹果官方的解释是Body是NSData类型,即可能为二进制内容,而且还没有大小限制,所以可能会很大,为了性能考虑,索性就拦截时就不拷贝了(内流满面脸)。为了解决这个问题,我们可以通过把Body数据放到Header中,不过Header的大小好像是有限制的,我试过2M是没有问题,不过超过10M就直接Request timeout了。。。而且当Body数据为二进制数据时这招也没辙了,因为Header里都是文本数据,另一种方案就是用一个NSDictionary或NSCache保存没有请求的Body数据,用URL为key,最后方法就是别用NSURLSession,老老实实用古老的NSURLConnection算了。。。

3. 使用NSURLProtocol时,在那两个类方法可以发送同步网络请求,而实例方法,如startLoading则进入死锁,直至超时,原因是执行实例方法所在的线程并没有启动runloop,而NSURLConnection这些网络请求需要依赖于runloop的,因此这些请求根本发不出去,所以必须使用异步请求,NSURLConnection/NSURLSession的异步请求的线程保证启动了runloop。

以上就是我目前发现的坑,欢迎大家补充,也希望对大家开发有所帮助哈~所幸的是NSURLProtocol对于大量并发的请求支持的还不错,不然就要弃用了~

下一篇就来继续讲讲本篇提到的CFHTTPMessageRef建立HTTP连接时的坑吧。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年07月18日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档