NSURLProtocol概述

 

一、概念

NSURLProtocol也是苹果众多黑魔法中的一种,使用它可以轻松地重定义整个URL Loading System。当你注册自定义NSURLProtocol后,就有机会对所有的请求进行统一的处理,基于这一点它可以让你:
1.自定义请求和响应
2.提供自定义的全局缓存支持
3.重定向网络请求
4.提供HTTP Mocking (方便前期测试)
5.其他一些全局的网络请求修改需求

二、如果注册多个URLProtocol会怎么样?

1.最后注册的Protocol最先执行。即倒序遍历。

2.如果其中一个Protocol的canInitWithRequest方法返回了YES,则后续的Protocol不再执行;否则会一直遍历,直到找到能处理此请求的Protocol。


三、使用方法

1.继承NSURLPorotocl,并注册你的NSURLProtocol

[NSURLProtocol registerClass:[MyURLProtocol class]];

当NSURLConnection准备发起请求时,它会遍历所有已注册的NSURLProtocol,询问它们能否处理当前请求。所以你需要尽早注册这个Protocol。

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

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];  
NSArray *protocolArray = @[ [MyURLProtocol 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:[MyURLProtocol class]];  

4.实现NSURLProtocol的相关方法

(1)当遍历到我们自定义的NSURLProtocol时,系统先会调用canInitWithRequest:这个方法。顾名思义,这是整个流程的入口,只有这个方法返回YES我们才能够继续后续的处理。我们可以在这个方法的实现里面进行请求的过滤,筛选出需要进行处理的请求。

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{if ([NSURLProtocol propertyForKey:MyURLProtocolHandled inRequest:request]){return NO;}if (![scheme hasPrefix:@"http"]){return NO;}return YES;
}

 

(2)当筛选出需要处理的请求后,就可以进行后续的处理,需要至少实现如下4个方法

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{return request;
}+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{return [super requestIsCacheEquivalent:a toRequest:b];
}- (void)startLoading
{NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];[NSURLProtocol setProperty:@(YES) forKey:MyURLProtocolHandled inRequest:mutableReqeust];self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}- (void)stopLoading
{[self.connection cancel];self.connection = nil;
}

说明:
(1)canonicalRequestForRequest: 返回规范化后的request,一般就只是返回当前request即可。
(2)requestIsCacheEquivalent:toRequest: 用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。
(3)startLoading和stopLoading 实现请求和取消流程。

5.实现NSURLConnectionDelegate和NSURLConnectionDataDelegate

因为在第二步中我们接管了整个请求过程,所以需要实现相应的协议并使用NSURLProtocolClient将消息回传给URL Loading System。在我们的场景中推荐实现所有协议。

- (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response
{if (response != nil){[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];}return request;
}- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{[self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{[self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[[self request] cachePolicy]];
}- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{[self.client URLProtocol:self didLoadData:data];
}- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{[self.client URLProtocolDidFinishLoading:self];
}- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{[self.client URLProtocol:self didFailWithError:error];
}

四、NSURLProtocol那些坑

坑1:企图在canonicalRequestForRequest:进行request的自定义操作,导致各种递归调用导致连接超时。这个API的表述其实很暧昧:
It is up to each concrete protocol implementation to define what “canonical” means. A protocol should guarantee that the same input request always yields the same canonical form.

所谓的canonical form到底是什么呢?而围观了包括NSEtcHosts和RNCachingURLProtocol在内的实现,它们都是直接返回当前request。在这个方法内进行request的修改非常容易导致递归调用(即使通过setProperty:forKey:inRequest:对请求打了标记)

坑2:没有实现足够的回调方法导致各种奇葩问题。如connection:willSendRequest:redirectResponse: 内如果没有通过[self client]回传消息,那么需要重定向的网页就会出现问题:host不对或者造成跨域调用导致资源无法加载。

坑3.崩溃报错:

0	libobjc.A.dylib objc_msgSend + 16
1	CFNetwork       CFURLProtocol_NS::forgetProtocolClient() + 124

有一点苹果说明的不是很清楚,苹果自己实现CustomHTTPProtocol源码中很好的体现了这一点:
NSURLProtocolClient回调动作必须跟请求的托管发送保持在一个线程、相同的Runloop,具体实现逻辑如下:
(1)在start方法中记录当前线程和Runloop模式;
(2)所有对于NSURLProtocolClient的回调,都在记录的线程、以相同的Runloop模式触发,使用如下方法:

[self performSelector:onThread:withObject:waitUntilDone:modes:];

坑4:httpBody
NSURLProtocol在拦截NSURLSession的POST请求时不能获取到Request中的HTTPBody。苹果官方的解释是Body是NSData类型,而且还没有大小限制。为了性能考虑,拦截时就没有拷贝。

 

坑5:Protocol请求拦截对证书认证方法的影响

因为URLConnection新增了证书认证方法:

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

但是NSURLProtocolClient并没有增加对应的回调方法,会导致原始请求的证书校验代理方法不调用。

暂时无正解。有解决方案的朋友欢迎骚扰。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500068.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用NSURLProtocol实现离线缓存

一、说明:NSURLProtocol可以拦截任何网络请求,包含UIWebView中发出的所有请求。但是在WKWebView中,只能拦截到最初始的请求,内嵌的资源下载拦截不到。比如通过WKWebView加载"http://www.baidu.com",则只能拦截到"h…

WKWebView概述

一、概述1.iOS 8 SDK中发布了新的WebView框架----WebKit.framework。2.WebKit使用WKWebView来代替IOS的UIWebView和OSX的NSWebView,并且使用Nitro JavaScript引擎,这意味着所有第三方浏览器运行JavaScript将会跟safari一样快。3.内存问题:(1)…

CoreData 自定义数据类型

在CoreData中,Entity中Attribute的类型只有固定的几种可选。如下图: 如果我们要想直接存放UIImage到数据库,如何做? 1.在coredata中新建的attribute中类形选择Transformable. 意思表示这个字段是自定义的类型。 2.在生成的NSMana…

XMLDictionary iOS的XML处理包

1.概述:XMLDictionary 提供一种简单的方法实现 iOS 和 Mac OS X 下解析和生成 XML 的方法。XMLDictionary 将 XML 转成 NSDictionary ,也可以将 NSDictionary 装成 XML 结构的字符串。2.实现原理:XMLDictionary 使用 NSXMLParser 类解析XML,使…

CoreData并发操作模式简介

iOS5.0中,苹果为CoreData的并发处理添加了两个内容。一、首先介绍第一个内容:CoreData框架中的NSManagedObjectContext类增加新的初始化方法:initWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct;1.参数方法介绍:…

java图片的导出,并压缩

java图片的导出,并压缩 java 压缩包jar包使用的是commons-compress-1.6.jar /*** 导出图片* param request* param response*/RequestMapping("/exportPicture")public void exportPicture(HttpServletRequest request,HttpServletResponse response) throws Exce…

【Linux系统基础】(3)在Linux上部署运维监控Zabbix和Grafana

目录 运维监控Zabbix部署简介安装安装前准备 - Mysql安装Zabbix Server 和 Zabbix Agenta. 安装Zabbix yum库b. 安装Zabbix Server、前端、Agentc. 初始化Mysql数据库d. 为Zabbix Server配置数据库e. 配置Zabbix的PHP前端 配置zabbix 前端(WEB UI) 运维监…

Pods简介

每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如 Java 语言的 Maven,nodejs 的 npm。随着 iOS 开发者的增多,业界也出现了为 iOS 程序提供依赖管理的工具,它的名字叫做:CocoaPods。 CocoaPods项目…

HashMap源码解释

HashMap 前言: 本文的hashMap是基于jdk1.7的hashMap. 关于jdk1.8的hashMap在另一篇中,那里将会介绍与1.7的差异与优势 首先基础知识介绍: 1.HashMap的成员变量   int DEFAULT_INITIAL_CAPACITY 16&#xff1a;默认的初始容量为2 ^ 4   int MAXIMUM_CAPACITY 1 <<…

MagicRecord For IOS 简介

一、概述 MagicalRecord 灵感来自于简洁的Ruby语言中 Rails Active Record 查询方式. MagicalRecord 这个开源库的核心思想是: 1.清除 Core Data 相关的代码2.简洁的清除,简单的一行搜索记录的功能3.当然允许使用NSFetchRequest,当存在着复杂的搜索条件时 二、使用 1. 导入框架…

对象引用 String引用 基本类型引用 差别

最近遇到一个线上问题,原因是忽略的引用的一些语法,导致出错,现在记录一下: Testpublic void testList(){List<String> list new ArrayList<String>();list.add("1");list.add("2");list.add("3");List<String> list2 new …

Mantle For iOS

Mantle可以很方便的去书写一个模型层的代码。 使用它可以很方便的去反序列化JSON或者序列化为JSON(需要在MTLModel子类中实现<MTLJSONSerializing>协议) 使用一个解释器MTLJSONAdapter去转换模型对象。 NSError *error nil; MyObject *myObject [MTLJSONAdapter modelO…

String的split方法的使用

1.引言 split方法很常用,记得我入职公司的时候,第一道笔试题就是关于字符串的拆分拼装等,这也足以说明,大公司对这些方法的重视程度. 其实我们平时一般用到的都是这些方法最简单的用法,但如果你不了解他的实现原理,碰到某些特殊问题终究是会被卡住的,于是就产生了所谓的bug,而…

ReactiveCocoa入门

概述 为什么要使用RAC&#xff1f;一个怪怪的东西&#xff0c;从Demo看也没有让代码变得更好、更短&#xff0c;相反还造成理解上的困难&#xff0c;真的有必要去学它么&#xff1f;相信这是大多数人在接触RAC时的想法。RAC不是单一功能的模块&#xff0c;它是一个Framework&am…

[前台]---input标签中的hidden,浏览器差异问题

前言: 这是一个比较简单的问题,也有人犯过这样的错误,如果你是一个人在编码,并且没有专门的去测试浏览器差异,这个或许会坑到你 问题描述: 用input标签的时候,需要把这个input隐藏掉,于是先这样做: <input hidden id"xxx" value"xxx"/> 这行代码…

C++和Objective-C混编(官方文档翻译)

苹果的Objective-C编译器允许用户在同一个源文件里自由地混合使用C和Objective-C&#xff0c;混编后的语言叫Objective-C。有了它&#xff0c;你就可以在Objective-C应用程序中使用已有的C类库。 Build Setting中要设定编译文件类型设置&#xff0c;如下图&#xff1a;Objectiv…

SpringMVC获取response的问题

SpringMVC获取response的问题: 关于用以下这种方式获取response的一些问题: ((ServletWebRequest) RequestContextHolder.getRequestAttributes()).getResponse(); 网上对于这种方式获取response的描述很多,我用的是jar包版本是3.2.9.release web.xml中肯定也是加了Request…

BigDecimal的个人总结

前言: 互联网公司,对于BigDecimal的使用,还是较为频繁的,那么就会涉及到关于这个类型的种种问题. 1:为什么使用BigDecimal 首先java八大基本类型真的很基本,4个整型搞不了小数,double和float搞的了小数,但搞不好,关键时刻就调链子,当然这也和他们存储方式有关(二进制无法精确的…

MagicRecord For IOS API深层解析

一、NSManagedObjectContext创建和获取1.默认上下文(每种只存在一个)&#xff08;1&#xff09;MR_rootSavingContext&#xff1a;此方法返回的上下文类型为NSPrivateQueueConcurrencyType(后台线程)&#xff0c;直接关联持久化协调器&#xff0c;对此上下文对象执行保存方法&a…

BeanUtils.copyProperties(对象A,对象B)

对于两个实例的复制,如果属性名字相同,则可以通过这个方法来操作,但是在使用的时候,一定要注意BeanUtils用的是哪个包的,因为常用的两个包,都有这个类和对应的方法,而复制方向却相反,所以一定要注意自动导包时选择的包!!!!!! 1.import org.springframework.beans.BeanUtils 这…