Пользовательский NSURLProtocol с NSURLSession

Я пытаюсь реализовать этот учебник, который реализует пользовательский NSURLProtocol с NSURLConnection.

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

Это работает, как и ожидалось, но теперь, когда NSURLConnection устарела в iOS9, я пытаюсь преобразовать его в NSURLSession.

К сожалению, это не сработало.

Я загружаю веб-сайт в uiwebview, если я использую NSURLConnection, он загружается и все работает как положено, все 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")
    }


    }

4 ответа

Решение

Я решил это.

Вот код, если кому-то это нужно.

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:

//  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)
      }
  }
}

Проблема с вашим кодом заключается в том, что вы используете NSURLSession.sharedSession для хранения вашей задачи с данными. Используя общий сеанс, вы не можете изменить делегат сеанса, поэтому ни одна из ваших подпрограмм делегата не будет вызываться.

Вам нужно будет создать пользовательский сеанс с вашим протоколом, установленным в качестве делегата для сеанса. Затем, когда вас попросят начать загрузку, вы можете создать задачу для данных в этом сеансе.

Из документации 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?
Другие вопросы по тегам