在 Apple 的文档 URL Loading System 中,详细介绍了一个请求是如何创建、发起、回传数据的,还包括了证书认证,cookie 等一系列的操作,但是好像唯独遗漏了一个特殊的类: NSURLProtocol ,其实在我们日常开发中,是有可能需要跟这个类打交道的,比如网页的缓存,request 重定向等一系列的需求。

关于 Apple 对 NSURLProtocol 的介绍

每次发起请求时,系统会寻找或者创建一个合适的 protocol 对象来响应 request,而在 protocol中,有一系列的方法会被调用,用来确定是否能够响应这次请求,并且允许通过返回参数来替换原本的请求。
NSURLProtocol是一个抽象类,里面定义许多发起请求时,系统自动回调的方法。我们需要创建一个继承自 NSURLProtocol的子类,并且实现某些方法,并且将类注册到系统中。系统发起请求时,按照注册的顺序,倒序询问所有的 protocol,假如能处理这次请求的话,就将处理转到这个类中。

创建一个 Protocol 对象

- initWithRequest:cachedResponse:client:

- initWithTask:cachedResponse:client:

当我们想在初始化的时候,自定义一些操作的话,我们可以重写上面这俩个方法,但是我们不需要手动调用初始化方法生成对象,这两个方法是系统会自动调用的。

向系统中注册 Protocol 类

+ (BOOL)registerClass:(Class)protocolClass

向系统中注册我们自定义的 protocol,只有这个类不是 NSURLProtocol子类时,这个方法才会返回 NO。当系统发起一次请求时,会按照注册顺序,倒序的调用子类的方法:canInitWithRequest ,第一个返回 YES 的类,会被确定为这次响应的 protocol。

Important

需要注意的是,当我们用NSURLProtocol发起请求时,注册方法应该如下:

1
2
3
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[objc_getClass("CustomURLProtocol")];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];

需要将自定义的 protocol 注册到 session 的 configuration.protocolClasses 中,才能起作用。

+ (void)unregisterClass:(Class)protocolClass

这个方法调用后,该类不再被系统询问。

决定一个 NSURLProtocol 子类是否支持响应某个 request

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

一个子类必须实现这个方法,来告诉系统,是否由该子类响应这次请求,返回 YES 的话,系统创建该类的对象,并且后续的类方法和对象方法会继续被回调,,返回 NO 的话,系统越过该子类,继续查找。

+ (BOOL)canInitWithTask

使用 NSURLSession发起请求时,会先调用这个方法,yes 的话,继续调用上面那个方法。功能与上线方法类似。

提供给系统一个标准化的 request

假如一个子类可以响应一个请求,系统会继续调用下面的方法:

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

在这个方法中,我们可以返回一个自定义的 request,系统会替换原有的,也可以直接返回原有的 request。

开始和停止请求

- (void)startLoading

当这个方法被调用时,子类必须发起请求,并通过 client ,将数据回传给系统,系统再将数据传递给本来的监听者。

- (void)stopLoading

这个方法被调用时,子类需要停止自己的请求。

总结

这个类是比较简单的一个类,可以当成是,苹果允许的一个中间人攻击,当发起一个请求时,我们可以通过注册 protocol ,替换这次请求,发起我们自己的请求,并通过 client ,将数据传给系统,这一切对 urlSessionDelegate 来说都是透明的。
简单的数据流通图