NSURLProtocol 拦截所有的 http

在你开发 iOS 应用过程中也许从没使用过 NSURLProtocol 更或者听说过它,但是它的能力十分强大。而这篇文章将会向你介绍什么是 NSURLProtocol ?如何使用 ?


URL Loading System

说明 NSURLProtocol 之前需要对 URL Loading System 进行说明,引用苹果官方文档对 URL Loading System 的一个解释:

The URL loading system is a set of classes and protocols that allow your app to access content referenced by a URL. At the heart of this technology is the NSURL class, which lets your app manipulate URLs and the resources they refer to.

大致的意思就是 URL Loading System 是由一系列的 classProtocol 组成,而我们可以通过这些 classProtocol 来操作相关的 url ,其中处于核心的 class 就是 NSURL

其中相关的 classProtocol 可以使用官方的一张图来说明:

2016-08-16-URL-loading-system.png

当然 URL Loading System 是由很多个方面组成的详细的情况可以直接查询苹果的官方文档 URL Loading System

而下面将要介绍的 NSURLProtocol 也是属于 URL Loading System 里面的一部分

NSURLProtocol

首先解释什么是 NSURLProtocol 官方的解释

An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.

每次在对一个 URL 进行请求的时候 URL Loading System 都会向一系列的 NSURLProtocol 询问是否可以处理该请求。如果该请求是需要处理的,那么就用相对应的 NSURLProtocol 来处理该请求。而 NSURLProtocol 是一个抽象类,我们不能直接初始化,我们需要继承他然后才可以使用。

在这里需要注意:首先苹果本身就已经提供了一些 NSURLProtocol 并不是只有我们自定义的 Protocol,其次我们自定的并且已经注册的 Protocol 在询问是否可以处理 URL 请求的时候是前于苹果提供的 Protocol 。

想要了解 NSURLProtocol 怎么使用那么要从以下几个方面去了解

  • 怎么选择感兴趣的请求进行处理
  • 怎么初始化
  • 初始化后怎么处理请求

选择感兴趣的请求

因为 NSURLProtocol 是抽象类所以我们需要继承他

class MyProtocol: NSURLProtocol {
}

在初始化 NSURLProtocol 之前则需要选择该请求是否你感兴趣的请求。那么怎么选择?
在每个 NSURLRequest 发送之前都会调用之前已注册的 Protocol 调用

public class func canInitWithRequest(request: NSURLRequest) -> Bool

该方法是一个类方法,再每个请求发送之前都会调用 Protocol 的这个方法,其中request 参数就是将要发送的请求。在这个方法里面就是要判断这个请求是否你所感兴趣的最后放回一个 Bool 值表明是否要处理这个请求。所以这个方法是在继承 NSURLProtocol 的时候是一定要重写这个方法的。

同时还需要在重写另外两个方法

public class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest
public class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool

第一个方法就是将请求规范化,这个方法则是返回规范化的请求。那么什么是规范化的请求,这一点就要看你的业务需求了,如果没有什么规范化的请求的话那么只要返回原来的请求就可以,而且一般也都是这么实现的。

第二个方法主要是判断两个请求是否为同一个请求,如果为同一个请求那么就会使用缓存数据。通常都是调用父类的该方法。

所以到这里为止一般的 NSURLProtocol 的内容都差不多是这样的

class MyProtocol: NSURLProtocol {

 public class func canInitWithRequest(request: NSURLRequest) -> Bool {
  //判断是否处理该请求
 }

 public class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
  //标准化该请求
 }

 public class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool {
  //判断两个请求是否相同
 }
}

初始化

如果在前面提到的方法中返回 true 那么说明 protocol 要处理该请求,那么接下来系统就会初始化该 protocol 。系统如何初始化就是调用 protocol 的

init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?)                                      

这个方法是系统调用的,在这个方法里面可以初始化 protocol 的一些需要用到的相关对象。最后需要调用父类的改方法来初始化。
所以到这里为止自定义的 protocol 需要以下几个方法

class MyProtocol: NSURLProtocol {

 public class func canInitWithRequest(request: NSURLRequest) -> Bool {
  //判断是否处理该请求
 }

 public class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
  //标准化该请求
 }

 public class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool {
  //判断两个请求是否相同
 }

 init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
  //初始化 protocol 相关数据
 }
}

处理请求

在决定要处理请求,而且也初始化以后就需要开始处理该请求了。在处理请求的时候需要用到下面两个方法 :

public func startLoading()
public func stopLoading()

这两个方法是在经过前面一系列方法后,protocol 开始处理请求时,系统调用的方法 startLoading() 是在开始处理的时候调用,而 stopLoading() 是在结束处理请求的时候调用的。

在处理请求的时候可以对请求进行更改,甚至或者重新生成一个请求。当然 NSURLProtocol 里面也提供了方法可以让我们方便的修改请求

class func property(forKey: String, in: URLRequest)
//返回请求中指定 key 的值

class func setProperty(_ value: Any, forKey key: String, in request: NSMutableURLRequest)
//设置某个 key 的值

class func removeProperty(forKey: String, in: NSMutableURLRequest)
//移除某个 key

以上的几个方法都已经够我们在修改请求的时候用了。那么接下来的问题就是系统如何知道我们处理好了请求然后调用 stopLoading() 方法。在 NSURLProtocol 中有一个成员变量 var client: URLProtocolClient? { get } 一个 URLProtocolClient 类型的变量。 URLProtocolClient 是个协议,在这个协议中已经定义好了一些可以让我们和系统进行交互的方法

public func URLProtocol( protocol : NSURLProtocol, wasRedirectedToRequest request: NSURLRequest, redirectResponse: NSURLResponse)
public func URLProtocol( protocol : NSURLProtocol, cachedResponseIsValid cachedResponse: NSCachedURLResponse)
public func URLProtocol( protocol : NSURLProtocol, didReceiveResponse response: NSURLResponse, cacheStoragePolicy policy: NSURLCacheStoragePolicy)
public func URLProtocol( protocol : NSURLProtocol, didLoadData data: NSData)
public func URLProtocolDidFinishLoading( protocol : NSURLProtocol)
public func URLProtocol( protocol : NSURLProtocol, didFailWithError error: NSError)
public func URLProtocol( protocol : NSURLProtocol, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
public func URLProtocol( protocol : NSURLProtocol, didCancelAuthenticationChallenge challenge: NSURLAuthenticationChallenge)

所以我们在 protocol 中对请求进行操作后,就是通过这个 client 与系统进行交互,通知系统什么时候完成数据的回复,或者使用已经缓存了的数据等等。
在系统得知了处理已经完成后,系统就会调用 stopLoading() 方法,在这个方法里面需要对处理请求做个结尾。
所以到此为止我们 protocol 应该是这个样子的:

class MyProtocol: NSURLProtocol {

 public class func canInitWithRequest(request: NSURLRequest) -> Bool {
  //判断是否处理该请求
 }

 public class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
  //标准化该请求
 }

 public class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool {
  //判断两个请求是否相同
 }

 init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
  //初始化 protocol 相关数据
 }

 public func startLoading() {
  //开始处理请求 用 client 与系统交互
 }

 public func stopLoading() {
  //结束处理 做一些数据的清理等。
 }
}

最后如果想要自己写的 protocol 起作用还需要注册即调用public class func registerClass(protocolClass: AnyClass) -> Bool方法,如果不想起作用还有public class func unregisterClass(protocolClass: AnyClass)方法取消注册。

NSURLProtocol 也是属于 iOS 中的一种黑魔法,本篇文章只是介绍最基本的使用方法,但具体的应用没有提及。NSURLProtocol 可以完成许多的事情。

  • 缓存
  • 记录 log 日志
  • 自定义协议

除了以上的点还有许多其他方面。

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

推荐阅读更多精彩内容