欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详解iOS开发之NSURLProtocol的那些坑

程序员文章站 2023-12-17 20:47:40
nsurlprotocol nsurlprotocol能够让你去重新定义苹果的url加载系统 (url loading system)的行为,url loading sy...

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

上一篇:

下一篇: