美文网首页
NSURLProtocol拦截器

NSURLProtocol拦截器

作者: 花大侠 | 来源:发表于2019-12-30 17:55 被阅读0次
    1iibhORPNGLz15jvM8h740Q-55eab7c5.png

    为了这篇文章的内容,写了一个小项目,支持ObjectiveC,Swift语言。

    支持Cocoapods集成,主要功能是,网络拦截和Mock数据。 有兴趣的同学可以看看。

    项目地址: https://github.com/zColdWater/HTTPInterceptor

    一,NSURLProtocol 的介绍

    Apple 官方文档 https://developer.apple.com/documentation/foundation/urlprotocol

    NSURLProtocol 是一个抽象类,你不能直接创建这个实例,如果你想使用它的功能,你应该继承它创建属于自己的子类,然后重写 NSURLProtocol 的方法。

    1.关于NSURLProtocol类定义

    我们看下这个类的定义:

    
    open class URLProtocol : NSObject {
    
        public init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?)
    
        // 操作被拦截协议的对象,用于给予被拦截者回调。
    
        open var client: URLProtocolClient? { get }
    
        // 拦截到的request
    
        open var request: URLRequest { get }
    
        // 给接收者一个缓存对象
    
        @NSCopying open var cachedResponse: CachedURLResponse? { get }
    
        // 通过URLRequest参数返回是否要拦截这个Request
    
        open class func canInit(with request: URLRequest) -> Bool
    
        // 是不是要重新返回一个新的request,一般你可以在原request基础上,加一些参数等,然后再返回,返回后,request属性就是你返回的request了。
    
        open class func canonicalRequest(for request: URLRequest) -> URLRequest
    
        // 比较两个URLRequest的缓存是否相当,如果相当,返回true,否则返回false。 目前还没想到有什么场景需要重写这个方法。
    
        open class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool
    
        // 开始你需求的操作了,可以通过self.request拿到拦截的request。
    
        open func startLoading()
    
        // 取消任务后,你要的动作。
    
        open func stopLoading()
    
        // 读取URLRequest的附加属性,通过Key
    
        open class func property(forKey key: String, in request: URLRequest) -> Any?
    
        // 设置URLRequest的附加属性,通过Key
    
        open class func setProperty(_ value: Any, forKey key: String, in request: NSMutableURLRequest)
    
        // 移除URLRequest的附加属性,通过Key
    
        open class func removeProperty(forKey key: String, in request: NSMutableURLRequest)
    
        // 将当前协议注册进拦截系统
    
        open class func registerClass(_ protocolClass: AnyClass) -> Bool
    
        // 取消注册 和 registerClass 动作相反
    
        open class func unregisterClass(_ protocolClass: AnyClass)
    
    }
    
    extension URLProtocol {
    
        @available(iOS 8.0, *)
    
        // 是否要拦截这个URLSessionTask,它和 canInit(with request: URLRequest) 只能有一个被调用,如果声明了  canInit(with task: URLSessionTask) 就不会走 canInit(with request: URLRequest) 了。
    
        open class func canInit(with task: URLSessionTask) -> Bool
    
        @available(iOS 8.0, *)
    
        public convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?)
    
        @available(iOS 8.0, *)
    
        // 获取拦截的task
    
        @NSCopying open var task: URLSessionTask? { get }
    
    }
    
    

    2.如何拦截呢,示例代码?

    那么我们一般怎么用它来拦截URL呢? 我写了一个示例代码,MyURLProtocol在页面最开始先注册好。 然后你可以创建任何已经有的标准协议URL还是你自定义的协议URL,都可以。 然后通过URLSession创建一个会话,再用这个会话loading你的URL,开始这个task。 MyURLProtocol 就能够拦截到你的URL了,下面的例子。

    例子输出:

    
    拦截的RequestURL字符串:myscheme://www.appcoda.com/working-url-schemes-ios/
    
    拦截的RequestURL字符串:http://www.appcoda.com/working-url-schemes-ios/
    
    拦截的RequestURL字符串:https://www.appcoda.com/working-url-schemes-ios/
    
    拦截的RequestURL字符串:file:///a/working-url-schemes-ios/
    
    拦截的RequestURL字符串:https://www.appcoda.com/working-url-schemes-ios/
    
    

    例子代码:

    
    import UIKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
    
            super.viewDidLoad()
    
            URLProtocol.registerClass(MyURLProtocol.self)
    
            // 自定义协议的URL
    
            let customUrl = URL(string: "myscheme://www.appcoda.com/working-url-schemes-ios/")!
    
            // 标准HTTP协议 URL
    
            let httpUrl = URL(string: "http://www.appcoda.com/working-url-schemes-ios/")!
    
            // 标准HTTPS协议 URL
    
            let httpsUrl = URL(string: "https://www.appcoda.com/working-url-schemes-ios/")!
    
            // 标准FILE协议 URL
    
            let fileUrl = URL(string: "file:///a/working-url-schemes-ios/")!
    
            // 分别创建URL的Task
    
            let customUrlTask = URLSession.shared.dataTask(with: customUrl)
    
            let httpUrlTask = URLSession.shared.dataTask(with: httpUrl)
    
            let httpsUrlTask = URLSession.shared.dataTask(with: httpsUrl)
    
            let fileUrlTask = URLSession.shared.dataTask(with: fileUrl)
    
            // 分别开始每个Task的任务
    
            customUrlTask.resume()
    
            httpUrlTask.resume()
    
            httpsUrlTask.resume()
    
            fileUrlTask.resume()
    
        }
    
    }
    
    class MyURLProtocol: URLProtocol {
    
        /// 重写 canInit ,用于判断这个 URLRequest 是不是需要拦截。 如果需要拦截返回true,否则返回false。
    
        override class func canInit(with request: URLRequest) -> Bool {
    
            print("拦截的RequestURL字符串:\(request.url!.absoluteString)")
    
            return false
    
        }
    
        /// 重写 canonicalRequest 根据当前的request返回一个新的request,当 canInit 返回true,表示需要拦截,才会走这里。
    
        override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    
            return request
    
        }
    
        /// 重写 startLoading 用于你的自定义操作,当 canonicalRequest 执行完,才会走这里。
    
        override func startLoading() {}
    
        /// 重写 stopLoading 用于你的自定义操作
    
        override func stopLoading() {}
    
    }
    
    

    3.做一个最简易版的Mock数据的例子

    例子输出:

    
    Mock Data:Optional("{\n  \"name\" : \"henry\"\n}")
    
    

    例子代码:

    
    import UIKit
    
    let customUrl = URL(string: "myscheme://www.appcoda.com/working-url-schemes-ios/")!
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
    
            super.viewDidLoad()
    
            URLProtocol.registerClass(MyURLProtocol.self)
    
            let customUrlTask = URLSession.shared.dataTask(with: customUrl) { (data, response, error) in
    
                let jsonStr = String(data: data!, encoding: .utf8)
    
                print("Mock Data:\(String(describing: jsonStr))")
    
            }
    
            customUrlTask.resume()
    
        }
    
    }
    
    class MyURLProtocol: URLProtocol {
    
        /// 重写 canInit ,用于判断这个 URLRequest 是不是需要拦截。 如果需要拦截返回true,否则返回false。
    
        override class func canInit(with request: URLRequest) -> Bool {
    
            guard let url = request.url else { return false }
    
            // 如果是我们想要拦截的URL,我们就返回true。
    
            if url == customUrl {
    
                return true
    
            }
    
            else {
    
                return false
    
            }
    
        }
    
        /// 重写 canonicalRequest 根据当前的request返回一个新的request,当 canInit 返回true,表示需要拦截,才会走这里。
    
        override class func canonicalRequest(for request: URLRequest) -> URLRequest {
    
            return request
    
        }
    
        /// 重写 startLoading 用于你的自定义操作,当 canonicalRequest 执行完,才会走这里。
    
        override func startLoading() {
    
            // 创建 HTTPURLResponse 对象,返回给被拦截的请求任务。
    
            let query: [String:String] = ["name":"henry"]
    
            let data = try! JSONSerialization.data(withJSONObject: query, options: [.prettyPrinted])
    
            let response: HTTPURLResponse = HTTPURLResponse(url: self.request.url!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil)!
    
            // self.client 你可以理解成被你拦截的对象,然后把数据塞给它,Response 和 Data。
    
            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
    
            self.client?.urlProtocol(self, didLoad: data)
    
            // 告诉被拦截对象,已经完成了。 这个时候,外面被拦截的请求 URLSession.shared.dataTask 也会得到完成的回调。
    
            self.client?.urlProtocolDidFinishLoading(self)
    
        }
    
        /// 重写 stopLoading 用于你的自定义操作
    
        override func stopLoading() {}
    
    }
    
    

    二,NSURLProtocol 的应用

    项目地址: https://github.com/zColdWater/HTTPInterceptor

    1.拦截器:

    比如拦截一些网络请求,集中为某些规则的URLRequest添加一些参数或者再更改URLResponse 等等操作。

    WechatIMG1089-2596db6c.png

    2.Mocker:

    比如服务器API还没有开发完成,自己Mock假数据为了展示UI使用。

    关于拦截器Mocker的应用,我写了一个开源项目,API清晰,使用简单,支持Cocoapods集成,支持Swift 和 ObjectiveC。

    WechatIMG1088-ef9a798b.png

    三,总结

    我再总结一下下哈。 首先是我写的项目真的挺好用,哈哈。

    第一步: 注册自己的URLProtocl子类,在触发之前。

    第二步: 在自己的子类里面完成拦截后的逻辑,比如是给个假数据,然后告诉拦截者获取数据完成了,还是不拦截,这个根据自己的需求。

    第三步: 触发拦截行为,首先定义一个属于你自己的URL,协议是选择http还是其他,还是自己的自定义协议都可以。然后创建URLSession会话,把这个URL搞进去,开始任务,发起获取数据的动作,这个时候就触发到拦截器啦。

    希望大家这个时候都能理解该如何使用这个东东了。

    相关文章

      网友评论

          本文标题:NSURLProtocol拦截器

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