NSURLProtocol和NSRunLoop的那些坑

iOS小技能:自定义NSURLProtocol无法监听AFNetworking请求的解决方案【方法交换SessionConfiguration的属性protocolClasses】 文章目录引言I 、解决方案1.1 问题分析1.2 解决方案II、intercept the HTTP/HTTPS requests 引言 需求背景:对网络请求的数据进行报文级别的加密:使用NSURLProtocol来自动监听HTTP请求并加密解密。 通过[NSURLProtocol registerClass: [KNURLProtocol class]];注入了自定义的NSURLProtocol类 +(void)addRequestBlock{ [self injectNSURLSessi 阅读详情

最近用AFNetworking替换掉了工程里的ASIHttpRequest,结果陆续碰到很多问题:

·如何统一地添加全局的HTTP头(不仅仅是UA而已)

·如何优雅地进行流量统计

·对特定的地址进行CDN加速(URL到IP的替换)

·怎么实现HTTP的同步请求

前三个需求对于ASIHttpReqeust来说都不是问题,只需要在几个统一的点进行修改即可。而使用AFNetworking后就没有那么容易了:一方面AFNetworking中生成NSURLRequest的点比较多,并没有一个统一的路径。其次工程中会有部分直接使用NSURLConnecion的场景,无法统一。经cyzju提醒发现了NSURLProtocol这个大杀器,可惜对应的文档过于简略,唯一比较详细的介绍就只有RW的这篇教程而已,掉了很多坑,值得记上一笔。

NSURLProtocol

概念

NSURLProtocol也是苹果众多黑魔法中的一种,使用它可以轻松地重定义整个URL Loading System。当你注册自定义NSURLProtocol后,就有机会对所有的请求进行统一的处理,基于这一点它可以让你

·自定义请求和响应

·提供自定义的全局缓存支持

·重定向网络请求

·提供HTTP Mocking (方便前期测试)

·其他一些全局的网络请求修改需求

使用方法

继承NSURLPorotocl,并注册你的NSURLProtocol

1
[NSURLProtocol registerClass:[YXURLProtocol class]];

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

实现NSURLProtocol的相关方法

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

1
2
3
4
5
6
7
8
9
10
11
12
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    if ([NSURLProtocol propertyForKey:YXURLProtocolHandled inRequest:request])
    {
        return NO;
    }
    NSString *scheme = [[request URL] scheme];
    NSDictionary *dict = [request allHTTPHeaderFields];
    return [dict objectForKey:@"custom_header"] == nil &&
    ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
     [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+ (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];
    [YXURLProtocol applyCustomHeaders:mutableReqeust];
    [NSURLProtocol setProperty:@(YES)
                        forKey:YXURLProtocolHandled
                     inRequest:mutableReqeust];
     
    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust
                                                    delegate:self];
}
- (void)stopLoading
{
    [self.connection cancel];
    self.connection = nil;
}

·canonicalRequestForRequest: 返回规范化后的request,一般就只是返回当前request即可。

·requestIsCacheEquivalent:toRequest: 用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。

·startLoading和stopLoading 实现请求和取消流程。

实现NSURLConnectionDelegate和NSURLConnectionDataDelegate

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
- (void)connection:(NSURLConnection *)connection
  didFailWithError:(NSError *)error
{
    [self.client URLProtocol:self
            didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
    if (response != nil) 
    {
        [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
    }
    return request;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
    return YES;
}
- (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];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.client URLProtocolDidFinishLoading:self];
}

在每个delgate的实现中我都刨去了工程中的特定实现(流量统计),只保留了需要实现的最小Protocol集合。

NSURLProtocol那些坑

从上面的介绍来看,NSURLProtocol还是比较简单,但是实际使用的过程中却容易掉进各种坑,一方面是文档不够详尽,另一方面也是对于苹果这套URL Loading Sytem并不熟悉,不能将整个调用过程有机地统一。

·坑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不对或者造成跨域调用导致资源无法加载。

同步AFNetworking请求

虽然Mattt各种鄙视同步做网络请求,但是我们不可否认某些场景下使用同步调用会带来不少便利。一种比较简单的实现是使用信号量做同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation AFHTTPRequestOperation (YX)
- (void)yxStartSynchronous
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        dispatch_semaphore_signal(semaphore);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        dispatch_semaphore_signal(semaphore);
    }];
    [self start];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
@end

但是这样带来的问题是在UI线程调用同步请求就会导致线程堵死崩溃(好吧,就不应该允许UI线程上这么做)。一种改进的方法是使用NSRunLoop

即:

1
2
3
4
5
while (_shouldBlock)
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:[NSDate distantFuture]];
   }

但是这种写法是有大坑的:如果当前NSRunLoop并没有任何NSTimer或Input Source,runMode:beforeDate:方法将立刻返回NO,于是造成死循环,占用大量CPU,进而导致NSURLConnection请求超时。 规避的方法是往RunLoop中添加NSTimer或者空NSPort使得NSRunLoop挂起而不占用CPU。(ASIHttpRequest就是在当前RunLoop中添加了0.25秒触发一次的刷新Timer)

If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately. OS X may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.

(via:阿毛的蛋疼地)

win11下的MySQL安装 目前官方最新版本是8.0.26,考虑到其稳定性、可靠性还需一定周期保证,所以使用官方版求稳定仍然建议5.7系列。3、选择MySQL Servers 5.7.38 -x64,然后按绿色箭头将选中的版本移植到右边选中框中。4、点击下方蓝色Advanced Options,选择安装位置。1、在开始界面搜索MySQL,点击command line。4、在“系统变量”中选择“path”,点击编辑。5、新建——>增加MySQL路径——>确定,12、execute,安装好后next。9、不需要做修改,直接点击next。. 阅读详情

相关推荐

NHANES数据库的介绍及使用(一)

一、数据库概况 NHANES(National Health and Nutrition Examination Survey)是一项旨在评估美国成人和儿童健康和营养状况的研究计划,计划始于20世纪60年代初期,是一项针对不同人群或健康主题的调查。 1999年该调查成为一项持续计划,涉及各种健康和营养测量,项目每年调查一个全国代表性的样本,约5000人,这些人群位于全国各县,每年对其中15个县进行访问。 NHANES访谈部分包括人口统计学、社会经济学、饮食和健康相关问题。体检部分包括生理测量、实验室检查等

Christina 5万+

iOS开发之NSURLProtocol的那些坑

接触过iOS系统中URL Loading System都知道,NSURLProtocol是如此地强大,可以拦截应用内几乎所有的网络请求(除了WKWebView),并可以修改请求头,返回client任意自定义的数据等等,据说很多做网络缓存都是利用这个类的。 那么,首先讲解一下NSURLProtocol怎么使用吧。 1. 定义一个NSURLProtocol的子类 在继承NSURLProtocol

xanxus46的专栏 1万+

PICO+Unity MR空间网格开发实战:从零构建透视交互场景

本文详细介绍了使用PICO 4 Ultra与Unity引擎进行MR空间网格开发的完整实战流程。从环境准备、项目初始化、核心组件配置,到空间网格的生成、可视化与物理交互实现,手把手教你构建一个能识别真实环境并发射虚拟小球的透视交互场景。文章重点解析了PICO SDK中空间网格功能的关键配置与优化技巧,并提供了真机部署与调试的实用指南。

e3f4g5的博客 490

自定义的NSURLProtocol问题优化

问题 自定义的urlprotocol在stoploading 上崩溃 解决方案 修改了canInitWithRequest方法 疑问 为什么注册失效,在appdelegate里面注册失效 [NSURLProtocol registerClass:[SBLogURLProtocol class]]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfigura

探究苹果技术原理和学习新技术(锲而舍之,朽木不折;锲而不舍,金石可镂。) 498

深度学习 tesorflow2.0版本 入门第一章

为什么要学tensflow 深度学习发展迅猛 框架灵活 google开发 公司应用广泛 2.0更加灵活 后面会学习的项目 图像分类 房价预测 泰坦尼克号生存预测 文本分类 文本生成 机器翻译 核心部分 模型训练 tesorboard----> 查看模型训练进度 模型保存和部署 分布式训练-----> 加速训练速度,提升训练效率 TensorFlow常用库:Tfds, tfhub,tensor2tensor 多平台云端环境GPU:Goolgle cloud环境配置,AWS环境配置 kaggle平

weixin_46969441的博客 1235

保姆级教程:用Ansible 2.9自动化配置华为ENSP路由器(附Cloud连接避坑指南)

本文提供了一份详细的Ansible 2.9与华为ENSP路由器自动化配置教程,涵盖环境搭建、网络连通性配置、Playbook开发及典型问题排查。特别针对Cloud连接常见问题提供避坑指南,帮助运维工程师快速掌握网络自动化技能,提升工作效率。

weixin_42561476的博客 375

NSURLConnection 网络请求

前言 DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection 从 iOS 9 开始 NSURLConnection 的大部分方法被废弃。 1、NSURLConnection NSURLConnection ...

diaochuanzhi7826的博客 376

CodeBuddy Code CLI 常用工作流:17个实战场景全解析

CodeBuddy Code CLI 17个实战场景解析 本文详细介绍了CodeBuddy Code CLI在17个开发场景中的高效应用,包括: 快速理解新代码库 高效修复Bug 安全重构代码 使用子代理系统处理专业任务 Plan Mode安全分析 自动化测试处理 创建合并请求 文档生成与维护 图片内容分析 文件目录快速引用 每个场景都配有具体命令示例和最佳实践,帮助开发者提升工作效率。

简简单单Onlinezuozuo 1078

【转】 NSURLProtocol和NSRunLoop的那些坑

转自:http://xiangwangfeng.com/2014/11/29/NSURLProtocol%E5%92%8CNSRunLoop%E7%9A%84%E9%82%A3%E4%BA%9B%E5%9D%91/ 参考:http://www.raywenderlich.com/59982/nsurlprotocol-tutorial 最近用AFNetworking替换掉了工程里的A...

weixin_30721899的博客 109

ABB机器人将实时坐标发送给西门子PLC的具体方法示例

ABB机器人将实时坐标发送给西门子PLC的具体方法示例

Robot_PLC_HMI_Automation 4912

ABAP面试题系列:写一组会出现死锁(Deadlock)的ABAP程序

我们在计算机操作系统这门专业课上,学过死锁(Deadlock)的概念:两个或两个以上的进程(或线程)在执行过程中,由于竞争资源而造成的一种阻塞的现象,称为死锁。若无外力干预,这些处于死锁状态的进程将永远处于互相等待的阻塞状态中。 正好我儿子走到我电脑前看到文章标题,好奇地问我什么是死锁。我解释道,“假设你和白妹妹(他的玩伴)手上都有一张奥特曼白金卡,你特别想要白妹妹手上那张白金卡,白妹妹也特别想...

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1474

NSURLConnection从入门到放弃

尽管NSURLConnection已经不建议使用了,iOS 7取而代之的是NSURLSession,但是本文依然打算剖析一下,其中一些思想还是值得学习一下,如果您不用NSURLConnection,并且也不打算尝试使用,可以跳过本文。 一、概念介绍 NSURLConnection是2003年随...

chenhui3831的博客 119

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

iOS应用内抓包、NSURLProtocol 拦截 APP 内的网络请求

iOS应用内抓包、NSURLProtocol 拦截 APP 内的网络请求

AFun_day的博客 1659

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

NSURLProtocol全攻略

title: NSURLProtocol 全攻略 author: 全凯 description: NSURLProtocol是URL Loading System的重要组成部分,具有非常强大的功能,本文全面介绍了NSURLProtocol的方方面面。 categories: iOS date: 2017/02/15 tags: iOS 网络 一位...

cpongo5 436

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

使用NSURLProtocol实现离线缓存

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

有志者事竟成-程序员也可以创造优雅的艺术 1673

NSURLProtocol拦截 HTTP 请求

NSURLProtocol是苹果为我们提供的URL Loading System的一部分,在每一个 HTTP 请求开始时,URL 加载系统会创建一个合适的NSURLProtocol对象处理对应的 URL 请求,正常情况下的网络请求是使用的系统默认实现, 而我们需要做的就是写一个继承自NSURLProtocol的类,并通过- registerClass:方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。 NSURLProtocol是...

gcs的博客 2560

NSURLProtocol拦截网络请求

1.原理NSURLProtocol 是苹果为我们提供的 URL Loading System 的一部分,在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我...

weixin_37664648的博客 1930

NSURLProtocol概述

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

有志者事竟成-程序员也可以创造优雅的艺术 5965

NSURLProtocol的总结

http://www.cnblogs.com/xiaxlsblog/archive/2013/08/09/NSURLProtocol-xiaxl.html NSURLProtocol是一个抽象类。NSURLProtocol是NSURLConnection的handler。   所有的通过NSURLConnection发出的请求都会走NSURLProtocol协议。UIWeb...

diaoju3333的博客 204

NSURLConnection详解

来源:http://blog.youkuaiyun.com/paulluo0739/article/details/6843312 URLRequest 的一个实例:(获取北京的天气情况) - (void)weather { NSURL *url = [NSURL URLWithString:@"http://www.weather.com.cn/adat/cityinfo/101010100.ht

iOS的脚步 680

有关NSURLConnection的几个问题

对NSURLConnection的几种网络请求方式做了测试,其中有关于同步请求方式sendSynchronousRequest,异步请求方式sendAsynchronousRequest,以及代理方式

逸轻紫的博客 751
上一篇: [OC]之 atomic 与 nonatomic的区别
下一篇: StarUML mac版本破解
lin_transform
博客等级 码龄9年 3粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值