【问题标题】:Custom NSURLProtocol with NSURLSession使用 NSURLSession 自定义 NSURLProtocol
【发布时间】:2023-03-22 14:01:01
【问题描述】:

我正在尝试实现本教程,该教程使用 NSURLConnection 实现自定义 NSURLProtocol。

https://www.raywenderlich.com/76735/using-nsurlprotocol-swift

它按预期工作,但现在 NSURLConnection 在 iOS9 中已弃用,我正在尝试将其转换为 NSURLSession。

不幸的是,它没有用。

我正在 uiwebview 中加载一个网站,如果我使用 NSURLConnection 它会加载并且一切都按预期工作,来自 webview 的所有 http 请求都会被捕获,但在使用 NSURLSession 时不会。

感谢任何帮助。

这是我的代码

    import UIKit

    class MyProtocol: NSURLProtocol, NSURLSessionDataDelegate, NSURLSessionTaskDelegate, NSURLSessionDelegate {

    //var connection: NSURLConnection!
    var mutableData: NSMutableData!
    var response: NSURLResponse!

    var dataSession: NSURLSessionDataTask!

    override class func canInitWithRequest(request: NSURLRequest) -> Bool {

        if NSURLProtocol.propertyForKey("MyURLProtocolHandledKey", inRequest: request) != nil {
            return false
        }

        return true
    }

    override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
        return request
    }

    override class func requestIsCacheEquivalent(aRequest: NSURLRequest,
        toRequest bRequest: NSURLRequest) -> Bool {
            return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
    }

    override func startLoading() {
        let newRequest = self.request.mutableCopy() as! NSMutableURLRequest
        NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)

        self.dataSession = NSURLSession.sharedSession().dataTaskWithRequest(newRequest)

        dataSession.resume()
        self.mutableData = NSMutableData()
    }

        override func stopLoading() {

        print("Data task stop")
        self.dataSession.cancel()
        self.mutableData = nil

    }

    func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
        self.response = response
        self.mutableData = NSMutableData()
        print(mutableData)
    }

    func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
        self.client?.URLProtocol(self, didLoadData: data)
        self.mutableData.appendData(data)
    }

    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        if (error == nil)
        {
            self.client!.URLProtocolDidFinishLoading(self)
            self.saveCachedResponse()
        }
        else
        {
            self.client?.URLProtocol(self, didFailWithError: error!)
        }
    }

    func saveCachedResponse () {
        let timeStamp = NSDate()
        let urlString = self.request.URL?.absoluteString
        let dataString = NSString(data: self.mutableData, encoding: NSUTF8StringEncoding) as NSString?
        print("TiemStamp:\(timeStamp)\nURL: \(urlString)\n\nDATA:\(dataString)\n\n")
    }


    }

【问题讨论】:

  • 你怎么知道你的代码不工作?你做了什么来自己追查问题?您可以回顾一下您的代码示例并删除所有注释掉的部分吗?也许添加一些关于您在每个例程中要完成的任务的 cmets。
  • 嘿,我是 swift 新手,面临 webview 缓存问题。我正在尝试使用相同的源,网页加载正确并保存在缓存中。但是当设备离线时,我无法从缓存数据中获取它。可能我必须用 urlsession 更新代码。你能帮我看看这个来源吗:(drive.google.com/file/d/0B-5GPXUpPZh-Q2FOWEJudXRaQkE/…

标签: ios swift nsurlsession nsurlprotocol nsurlsessiondatatask


【解决方案1】:

您的代码遇到的问题是您使用 NSURLSession.sharedSession 来包含您的数据任务。通过使用共享会话,您无法更改会话委托,因此您的任何委托例程都不会被调用。

您需要创建一个自定义会话,并将您的协议建立为会话的委托。然后,当被要求开始加载时,您可以在该会话中创建数据任务。

【讨论】:

  • 嗨,感谢您的回复,注释掉的代码是针对 NSURLConnection 的,因为它在 iOS9 中已被弃用我需要将其转换为 NSURLSession,我正在做的是我想监控所有 http 请求从 uiwebview 并保存来自某些 url 请求的 JSON 响应。该代码使用 NSURLConnection 运行良好,但现在已弃用。使用 NSURLConnection 我可以看到请求和 webview 加载网站和点击的任何链接。但是随着对 NSURLSession 的更改,webview 不会加载网站。
  • 是的,我明白了。正如我在回答中所说,当您使用 NSURLConnection 时,您为连接设置了委托,并且您的委托方法收集了数据。在新的基于 NSSession 的代码中,您需要创建一个自定义会话而不是使用 NSSession.sharedSession 以便您可以在该会话上设置委托,以便可以调用您的会话和数据任务委托回调。
  • 嘿,scott thomson,你能告诉我如何在设备离线时使用 startLoading() 方法来显示缓存数据本地备份吗?这是代码,我是 swift 新手,对此有疑问。啧啧:raywenderlich.com/76735/using-nsurlprotocol-swift
【解决方案2】:

我已经解决了。

如果有人需要,这里是代码。

import Foundation

class MyProtocol1: NSURLProtocol, NSURLSessionDataDelegate, NSURLSessionTaskDelegate
{
private var dataTask:NSURLSessionDataTask?
private var urlResponse:NSURLResponse?
private var receivedData:NSMutableData?

class var CustomKey:String {
    return "myCustomKey"
}

// MARK: NSURLProtocol

override class func canInitWithRequest(request: NSURLRequest) -> Bool {
    if (NSURLProtocol.propertyForKey(MyProtocol1.CustomKey, inRequest: request) != nil) {
        return false
    }

    return true
}

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
    return request
}

override func startLoading() {

    let newRequest = self.request.mutableCopy() as! NSMutableURLRequest

    NSURLProtocol.setProperty("true", forKey: MyProtocol1.CustomKey, inRequest: newRequest)

    let defaultConfigObj = NSURLSessionConfiguration.defaultSessionConfiguration()
    let defaultSession = NSURLSession(configuration: defaultConfigObj, delegate: self, delegateQueue: nil)

    self.dataTask = defaultSession.dataTaskWithRequest(newRequest)
    self.dataTask!.resume()

}

override func stopLoading() {
    self.dataTask?.cancel()
    self.dataTask       = nil
    self.receivedData   = nil
    self.urlResponse    = nil
}

// MARK: NSURLSessionDataDelegate

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask,
                didReceiveResponse response: NSURLResponse,
                                   completionHandler: (NSURLSessionResponseDisposition) -> Void) {

    self.client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)

    self.urlResponse = response
    self.receivedData = NSMutableData()

    completionHandler(.Allow)
}

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
    self.client?.URLProtocol(self, didLoadData: data)

    self.receivedData?.appendData(data)
}

// MARK: NSURLSessionTaskDelegate

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
    if error != nil && error!.code != NSURLErrorCancelled {
        self.client?.URLProtocol(self, didFailWithError: error!)
    } else {
        saveCachedResponse()
        self.client?.URLProtocolDidFinishLoading(self)
    }
}

// MARK: Private methods

/**
 Do whatever with the data here
 */
func saveCachedResponse () {
    let timeStamp = NSDate()
    let urlString = self.request.URL?.absoluteString
    let dataString = NSString(data: self.receivedData!, encoding: NSUTF8StringEncoding) as NSString?
    print("TimeStamp:\(timeStamp)\nURL: \(urlString)\n\nDATA:\(dataString)\n\n")
}


}

【讨论】:

  • 谢谢哥们。帮助很大。以下是同一类的 swift 3 版本,以防万一有人需要。
  • 你真的不需要为每个请求使用单独的 NSURLSession。这是非常低效的。您应该考虑使用单个共享实例(使用在 dispatch_once 块中初始化的全局变量)。
  • 是否可以拦截来自移动文档目录的文件请求?它只拦截带有 http、https、file:///var/container/... 的 url,而不是 file:///var/mobile//Containers/Data/Application/...
【解决方案3】:

Swift 3 版本:

//  CustomURLProtocol.swift

class CustomURLProtocol: URLProtocol, URLSessionDataDelegate, URLSessionTaskDelegate {
  private var dataTask: URLSessionDataTask?
  private var urlResponse: URLResponse?
  private var receivedData: NSMutableData?

  class var CustomHeaderSet: String {
      return "CustomHeaderSet"
  }

  // MARK: NSURLProtocol

  override class func canInit(with request: URLRequest) -> Bool {
      guard let host = request.url?.host, host == "your domain.com" else {
          return false
      }
      if (URLProtocol.property(forKey: CustomURLProtocol.CustomHeaderSet, in: request as URLRequest) != nil) {
          return false
      }

      return true
  }

  override class func canonicalRequest(for request: URLRequest) -> URLRequest {
      return request
  }

  override func startLoading() {

      let mutableRequest =  NSMutableURLRequest.init(url: self.request.url!, cachePolicy: NSURLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 240.0)//self.request as! NSMutableURLRequest

      //Add User Agent

      var userAgentValueString = "myApp"
     mutableRequest.setValue(userAgentValueString, forHTTPHeaderField: "User-Agent")

      print(mutableRequest.allHTTPHeaderFields ?? "")
      URLProtocol.setProperty("true", forKey: CustomURLProtocol.CustomHeaderSet, in: mutableRequest)
      let defaultConfigObj = URLSessionConfiguration.default
      let defaultSession = URLSession(configuration: defaultConfigObj, delegate: self, delegateQueue: nil)
      self.dataTask = defaultSession.dataTask(with: mutableRequest as URLRequest)
      self.dataTask!.resume()

  }

  override func stopLoading() {
      self.dataTask?.cancel()
      self.dataTask       = nil
      self.receivedData   = nil
      self.urlResponse    = nil
  }

  // MARK: NSURLSessionDataDelegate

  func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
                  didReceive response: URLResponse,
                  completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {

      self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)

      self.urlResponse = response
      self.receivedData = NSMutableData()

      completionHandler(.allow)
  }

  func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
      self.client?.urlProtocol(self, didLoad: data as Data)

      self.receivedData?.append(data as Data)
  }

  // MARK: NSURLSessionTaskDelegate

  func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
      if error != nil { //&& error.code != NSURLErrorCancelled {
          self.client?.urlProtocol(self, didFailWithError: error!)
      } else {
          //saveCachedResponse()
          self.client?.urlProtocolDidFinishLoading(self)
      }
  }
}

【讨论】:

  • 它正在工作,但显示完整的网站。不显示移动网站视图。
【解决方案4】:

来自 URLSession 的文档:

重要

会话对象保持对委托的强引用,直到您的应用退出或显式使会话无效。如果您不使会话无效,则您的应用会泄漏内存,直到退出。

还有:

注意

注意不要创建超出您需要的会话。例如,如果您的应用有多个部分需要类似配置的会话,请创建一个会话并在它们之间共享。

所以我会将 URLSession 的创建从 startLoading 方法移到 URLProtocol 子类初始化器:

class MyURLProtocol: URLProtocol, URLSessionDataDelegate,URLSessionTaskDelegate {
    override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
        super.init(request: request, cachedResponse: cachedResponse, client: client)
        defaultSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
    }
    private var defaultSession: URLSession?

【讨论】:

    猜你喜欢
    • 2015-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-02
    • 1970-01-01
    • 2016-06-15
    • 1970-01-01
    相关资源
    最近更新 更多