Пользовательский 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?