详解iOS开发之NSURLProtocol的那些坑

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做自定义的操作。

  1. 重定向网络请求
  2. 忽略网络请求,使用本地缓存
  3. 自定义网络请求的返回结果
  4. 一些全局的网络请求设置

接触过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]];
}

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

2. 在网络请求前注册NSURLProtocol

 // 注册拦截请求的NSURLProtocol
[NSURLProtocol registerClass:[CFHttpMessageURLProtocol class]]; 

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

// 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

[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对于大量并发的请求支持的还不错,不然就要弃用了~希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • iOS NSURLProtocol的具体使用方法详解

    本文介绍了iOS NSURLProtocol的具体使用方法详解,分享给大家,具体如下: NSURLProtocol定义 这两天在优化项目,无意间看到了NSURLProtocol,学习一下顺便总结下来. NSURLProtocol也是苹果众多黑魔法中的一种,能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSes

  • 详解iOS开发之NSURLProtocol的那些坑

    NSURLProtocol NSURLProtocol能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSU

  • 实例详解IOS开发之UIWebView

    iOS开发之UIWebView 是本文要介绍的内容,UIWebView是iOS sdk中一个最常用的控件.是内置的浏览器控件,我们可以用它来浏览网页.打开文档等等.这篇文章我将使用这个控件,做一个简易的浏览器.如下图: 我们创建一个Window-based Application程序命名为:UIWebViewDemo UIWebView的loadRequest可以用来加载一个url地址,它需要一个NSURLRequest参数.我们定义一个方法用来加载url.在UIWebViewDemoViewC

  • 详解 Corba开发之Java实现Service与Client

    详解 Corba开发之Java实现Service与Client 1      概述 CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)是由OMG组织制订的一种标准的面向对象应用程 序体系规范.或者说 CORBA体系结构是OMG为解决分布式处理环境(DCE)中,硬件和软件系统的互连而提出的一种解决方案. OMG:Object Management Group,对象管理组织.是一个国际化的.开放成员的.非盈利性的计算机行业标准协

  • 详解微信开发之Author网页授权

    微信开发中,经常有这样的需求:获得用户头像.绑定微信号给用户发信息.. 那么实现这些的前提就是授权! 1.配置安全回调域名: 在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的"开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息"的配置选项中,修改授权回调域名,值得注意的是这里就是直接写全域名,如: www.liliangel.cn.然而我们开发h5中一般用的是二级域名,如:h5.liliangel.cn 也同样在安全回调域名中. 2.用户级授权

  • 详解Android开发之MP4文件转GIF文件

    一 基本实现原理 在介绍具体实现过程之前,先简单说下基本原理和实现步骤,在解决相对比较复杂的问题,我习惯先理清主要原理步骤,不要一开始就被繁琐细节绊住,待具体实现时再逐个攻破.下面是主要步骤: 1.视频文件的读取:包括录制和本地文件读取 2.将需要转换的视频部分解析为 Bitmap 序列 3.将解析好的 Bitmap 序列编码生成 GIF 文件 二 视频文件的读取 视频文件的读取比较简单,没什么特别需要说的地方,这里简单贴出视频读取的核心部分代码,详细实现可以Google一下就行了. priva

  • 详解微信开发之access_token之坑

    首先不得不提到access_token的分类,一是普通access_token,二是网页授权access_token.其中前者是用于调用微信提供的各种借口,作为开发者的调用凭证,一般有效期为7200S,获取次数受限:另一种是第三方网页若需要使用用户微信账户登录,需要获取该access_token从而来获取用户微信账户信息.这个一定得区分开. 另外获取用户微信账户信息也有两种情况,一是普通的获取用户信息,它只需要调用微信用户信息接口即可获取,因而使用到的是第一种普通access_token,另一种

  • IOS 开发之UISearchBar 详解及实例

    IOS UISearchBar 详解 iPhone开发之UISearchBar学习是本文要学习的内容,主要介绍了UISearchBar的使用,不多说,我们先来看详细内容.关于UISearchBar的一些问题. 1.修改UISearchBar的背景颜色 UISearchBar是由两个subView组成的,一个是UISearchBarBackGround,另一个是UITextField. 要IB中没有直接操作背景的属性.方法是直接将 UISearchBarBackGround移去 seachBar=

  • IOS 开发之swift中手势的实例详解

    IOS 开发之swift中手势的实例详解 手势操作主要包括如下几类 手势 属性 说明 点击 UITapGestureRecognizer numberOfTapsRequired:点击的次数:numberOfTouchesRequired:点击时有手指数量 设置属性 numberOfTapsRequired 可以实现单击,或双击的效果 滑动 UISwipeGestureRecognizer direction:滑动方向 direction 滑动方向分为上Up.下Down.左Left.右Right

  • IOS 开发之Swift 元组的实例详解

    IOS 开发之Swift 元组的实例详解 元组是多个值组合而成的复合值.元组中的值可以是任意类型,而且每一个元素的类型可以是不同的. 元组声明 //普通声明 var point = (5,2) var httpResponse = (404, "Not Found") //定义类型声明 var point2 : (Int,Int,Int) = (10,5,2) var httpResponse2 : (Int,String) = (200,"ok") 元组解包 va

  • IOS 开发之PickerView自定义视图的实例详解

    IOS 开发之PickerView自定义视图的实例详解 例如选择国家,左边是名称右边是国家,不应该使用两列,而是自定义PickerView的一列,可以通过xib来实现. 注意,虽然PickerView也是一列,但是数据源方法是@required,所以必须实现. 因此,核心思想就是一列,自定义PickerView的行视图. 使用viewForRow方法可以设定行视图. 这样的视图可以通过xib和它的控制器进行封装: Xib的控制器继承自UIView类即可. 控制器维护一个用于设置数据的模型对象fl

随机推荐