iOS:NSURLProtocol搭建网络请求监控系统

这篇文章介绍了些什么?

通过这篇文章,你将会了解到一种对原代码毫无入侵的网络请求性能监控方案NSURLProtocol
以及:

1.NSURLProtocol是什么和其在URL Loading System中的作用

2.NSURLProtocol中最重要的几个API介绍

3.如何在集成AFNetworking等第三方网络库的项目中使用NSURLProtocol

4.如何通过NSURLProtocol处理自定义的scheme,而不发送真正的网络请求

一、什么是NSURLProtocol?

1.URL Loading System

URL-loading-system.png

援引一段官网介绍:

注:本文出现的中文版Apple文档均为Google翻译结果,可能有些语病,但基本上不影响阅读和理解,想阅读原文,可以点击本文提供的超链

0.png

简而言之:URL Loading System的作用就是与服务器进行通信

2.URL Loading System中的Protocol

5.jpg

NSURLProtocol作为Client和Server的中间层,接收Client发送的Request,将其发送至Server端,并接收Server端发送的Response,将数据传回Client端

2.NSURLProtocol官方文档

1.png
2.png
3.png
4.png

概要:NSURLProtocol虽然命名为Protocol但其实它是一个抽象类,正如文档介绍所说,不要直接实例化NSURLProtocol,正确的使用姿势是,创建NSURLProtocol的子类,通过registerClass:方法将其注册到URL Loading System中,系统会创建协议对象来处理相应的URL请求,我们可以通过NSURLProtocol提供的API接口,来达到存储和检索特定协议的请求数据的目的。

3.NSURLProtocol适用的场景

①基于URL Loading System的网络请求

②UIWebView

二、NSURLProtocol的工作流程

1.核心API介绍

NSURLProtocol.h

①拦截请求并发送

在URL Loading System发送网络请求后,通过protocol中拦截请求并处理,再手动的将请求发送出去

@interface NSURLProtocol : NSObject

//所有注册此Protocol的请求都会经过这个方法的判断
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;

//对需要拦截的请求进行自定的处理
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;

/**
    初始化protocol实例,所有来源的请求都以NSURLRequest形式接收
    
    @param client The NSURLProtocolClient object that serves as the
    interface the protocol implementation can use to report results back
    to the URL loading system
*/
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client;

/**
    开始请求
    在这里需要我们手动的把请求发出去,可以使用原生的NSURLSessionDataTask,也可以使用的第三方网络库
    同时设置"NSURLSessionDataDelegate"协议,接收Server端的响应
*/
- (void)startLoading;

//请求被停止
- (void)stopLoading;

②接收响应并转发

当接收到Server端的响应时,将其通过"NSURLProtocolClient"协议,转发给URL Loading System

@protocol NSURLProtocolClient <NSObject>

//发生重定向时
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;

//接收到响应时
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;

//接收到数据时
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;

//成功
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
//失败
- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;

"NSURLProtocolClient"的协议方法与"NSURLSessionDataDelegate"的协议方法基本上是一一对应的

- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;为例:

/**
    1️⃣我们在`- (void)startLoading`方法中,实现了"NSURLSessionDataTask"的协议
    在接收到Sever端的响应时,首先响应"NSURLSessionDataTask"的协议方法
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    //2️⃣通过NSURLProtocolClient转发给URL Loading System
    [[self client] URLProtocol:self didLoadData:data];
}

③注册Protocol到URL loading system

在实现上述方式后,通过[NSURLProtocol registerClass:self]将protocol注册到URL loading system中

2.注意事项

①线程同步问题

URL Loading System发出请求与接收响应要在同一线程

//创建NSURLSessionDataTask实例
- (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id<NSURLSessionDataDelegate>)delegate modes:(NSArray *)modes
{
    self = [super init];
    if (self != nil) {
        //在开始请求前记录当前线程
        self->_thread = [NSThread currentThread];
    }
    return self;
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    //在接收响应时,同步到发送请求时线程
    [self performSelector:@selector(performBlockOnClientThread:) onThread:self.thread withObject:[block copy] waitUntilDone:NO modes:self.modes];
}

②AFNetworking、Alamofire等第三方网络库发出的网络请求无法进入到自定义NSURLProtocol的问题

以AFNetworking举例:

 NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
 NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];

NSURLSessionConfiguration作为参数初始化的NSURLSession,在configuration对象中有属性protocolClasses

@property (nullable, copy) NSArray<Class> *protocolClasses;

protocolClasses官方文档

6.png

也就是说,我们监控网络是通过注册NSURLProtocol来实现的,但是通过sessionWithConfiguration:得到的session,它的configuration中已经有一个NSURLProtocol,所以它不会走我们的protocol中来!

解决方案:

创建NSURLSessionConfiguration的子类,hookprotocolClassesget方法,将我们的protocol返回

- (NSArray *)protocolClasses {
    // 如果还有其他的监控protocol,也可以在这里加进去
    return @[[CustomHTTPProtocol class]];
}

3.两种场景

①真正的网络请求

②模拟的网络请求

- (void)startLoading中不发送真正的网络请求,而是对自定义的请求进行解析!

比如通过自定协议实现页面的跳转。在实际业务中,会有这样的需求,前端页调起APP,回传给客户端一个自定的协议,客户端解析协议,并跳转至对应页,假如自定协议:gcsdeveloper://handoff/openControllerA

我们可以通过Protocol实现类似URL请求的方式来跳转至ControllerA

只需要两步:

1.在canInitWithRequest中判断Scheme == gcsdeveloper时返回YES,拦截请求

2.在startLoading中发现是自定义协议,不发送真正的网络请求,而且走自定协议的解析流程

三、NSURLProtocol的应用

统计APP内所有网络请求的失败率、响应时间等数据

通过拦截后统一重发,可以在对应时机添加方法,在这个过程中记录各类数据

其他应用

1.防止DNS劫持

2.自定义请求和响应

3.本地Mock数据

4.网络的缓存处理

5.重定向网络请求

6.过滤掉一些非法请求

7.使UIWebView的网络图片也享受XXWebImage的图片缓存池

四、NSURLProtocol的不足

1.并不是真正的拦截了所有的网络请求,比如WKWebview、AVPlayer等发出的网络请求并不能被拦截

2.系统没有提供对个性化配置的网络请求的兼容,需要手动处理(Hook)

3.增加一定的网络延迟,主要在于方法间的调用

五、参考内容

官方文档

NSProtocol苹果官方Demo

NSURLProtocol官方文档

protocolClasses官方文档

URL Loading System

相关文章

iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol

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

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

如何进行 HTTP Mock(iOS)

特别鸣谢:谷歌翻译

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容