详解iOS开发之NSURLProtocol的那些坑

 更新时间:2017年11月14日 11:00:06   作者:davelam1991  
本篇文章主要介绍了详解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对于大量并发的请求支持的还不错,不然就要弃用了~希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:

相关文章

  • iOS应用开发中SQLite的初步配置指南

    iOS应用开发中SQLite的初步配置指南

    这篇文章主要介绍了iOS应用开发中SQLite的初步配置指南,SQLite是一个极轻量级可作嵌入式的数据库,非常适合入门开发者使用,需要的朋友可以参考下
    2015-12-12
  • Ios苹果app应用程序开发者如何获取IPA签名证书详解

    Ios苹果app应用程序开发者如何获取IPA签名证书详解

    这篇文章主要为大家介绍了Ios苹果app应用程序开发者如何获取IPA签名证书详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • iOS Swift创建代理协议的多种方式示例

    iOS Swift创建代理协议的多种方式示例

    这篇文章主要给大家介绍了关于iOS Swift创建代理协议的多种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-12-12
  • iOS开发之时间戳(或date)转字符串的实例代码

    iOS开发之时间戳(或date)转字符串的实例代码

    这篇文章主要介绍了iOS开发之时间戳(或date)转字符串的实例代码,需要的朋友可以参考下
    2017-10-10
  • iOS中使用对象的弱引用示例代码

    iOS中使用对象的弱引用示例代码

    这篇文章主要给大家介绍了关于iOS中使用对象的弱引用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-01-01
  • iOS 图片压缩方法的示例代码

    iOS 图片压缩方法的示例代码

    本篇文章主要介绍了iOS 图片压缩方法的示例代码,主要有两种压缩图片的方法,有兴趣的可以了解一下,有兴趣的可以了解一下。
    2017-01-01
  • LRecyclerView侧滑iOS阻塞效果不完整的解决办法

    LRecyclerView侧滑iOS阻塞效果不完整的解决办法

    这篇文章主要介绍了LRecyclerView侧滑iOS阻塞效果不完整的解决办法,非常不错,具有参考借鉴价值,需要的朋友参考下
    2016-12-12
  • iOS如何定义名为任意的变量详解

    iOS如何定义名为任意的变量详解

    这篇文章主要给大家介绍了关于iOS如何定义名为任意的变量的相关资料,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2018-05-05
  • iOS制作带弹跳动画发布界面

    iOS制作带弹跳动画发布界面

    这篇文章主要为大家详细介绍了iOS制作带弹跳动画发布界面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • iOS WKWebView秒开方案实战记录

    iOS WKWebView秒开方案实战记录

    从iOS8开始,就引入了新的浏览器控件WKWebView,用于取代UIWebView,下面这篇文章主要给大家介绍了关于iOS WKWebView秒开方案的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2021-12-12

最新评论