NSURLProtocol 拦截所有的 http

作者: JasonWu | 来源:发表于2016-02-16 15:29 被阅读2340次

    在你开发 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 日志
    • 自定义协议

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

    相关文章

      网友评论

      本文标题:NSURLProtocol 拦截所有的 http

      本文链接:https://www.haomeiwen.com/subject/lyvpkttx.html