程序员iOS开发之常用技术点

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

2018-11-29  本文已影响18人  GCS_DEVELOPER

这篇文章介绍了些什么?

通过这篇文章,你将会了解到一种对原代码毫无入侵的网络请求性能监控方案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)

特别鸣谢:谷歌翻译

上一篇下一篇

猜你喜欢

热点阅读