I'm fetching xml data from server by using NSURLSession and NSURLSessionDelegate. Depends on some conditions I'm connecting with server. If I'm connecting with server everything works fine without any error but if I'm not connecting (depends on condition) to server and moving to another View Controller (by using storyboard?.instantiateViewControllerWithIdentifier(id)) I'm getting the following IOS error:
'A background URLSession with identifier backgroundSession already exists!'
Here is my code:
class MainClass: UITableViewController, NSURLSessionDelegate {
var task_service = NSURLSessionDataTask?()
override func viewDidLoad() {
super.viewDidLoad()
if(condition) {
getXMLFromServer()
}
}
func getXMLFromServer(){
task_service = getURLSession().dataTaskWithRequest() {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
// Fetching data from server
// In the end
self.session.invalidateAndCancel()
}
}
}
func getURLSession() -> NSURLSession {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 30.0
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
return session
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)) // Bypassing SSL error
}
}
EDIT: Found the reason for the error.
Error occurred because of the creation of NSURLSession in the Called View Controller.Called VC contains code to download PDF from server. But I don't know how to solve this. Below is the code of Called VC
class MainFormsController: UIViewController, UIPickerViewDelegate, UITextFieldDelegate, NSURLSessionDownloadDelegate, UIDocumentInteractionControllerDelegate, MFMailComposeViewControllerDelegate{
var download_task = NSURLSessionDownloadTask?()
var backgroundSession = NSURLSession()
override func viewDidLoad() {
super.viewDidLoad()
createNSURLSession()
}
/** Error occurred while creating this NSURLSession **/
func createNSURLSession() {
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSession")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
}
func downloadPDF() {
//Download PDF
download_task = backgroundSession.downloadTaskWithURL(url)
download_task?.resume()
}
}
Add this code in your MainFormsController:
deinit {
self.backgroundSession.finishTasksAndInvalidate();
}
Your code probably calls createNSURLSession() more than once which invalidate the NSURLSession behavior, as documentation says:
"You must create exactly one session per identifier (specified when
you create the configuration object). The behavior of multiple
sessions sharing the same identifier is undefined."
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html
Make sure createNSURLSession is called only once (singletone) for the life cycle of your app.
I think you already have an URLSession with identifier backgroundSession. First Call
- (void)invalidateAndCancel
on that. and then try with your code.
Related
For client certificate authentication I've to use URLSessionDelegate in the custom class for handling all requests. The problem is the class is not deinitialize after a request made.
Code:
class Request: NSObject, URLSessionDelegate, URLSessionTaskDelegate{
func request(){
let url:URL = URL(string: "https://stackoverflow.com")!
let config = URLSessionConfiguration.default
URLSession(configuration: config, delegate: self, delegateQueue: .main).dataTask(with: url) { (data, response, error) in
print("Received")
}.resume()
}
func request2(){
let url:URL = URL(string: "https://stackoverflow.com")!
let config = URLSessionConfiguration.default
URLSession(configuration: config).dataTask(with: url) { (data, response, error) in
print("Received")
}.resume()
}
deinit {
print("Class deinit...")
}
}
When calling Request().request() then deinit is not called. Calling Request().request2() then deinit is called.
I am not sure how to resolve this problem. Please help me to find out the solution. Thank you...
URLSession keeps a strong reference to its delegate (self in your case). Check the official documentation:
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until the app terminates.
You can invalidate the session using its methods finishTasksAndInvalidate() or invalidateAndCancel(). After that URLSession will release the strong reference to its delegate.
I guess you code example is just to demonstrate the behavior, but anyway I have to mention it's not a good practice to create a new URLSession for every single URLRequest.
In request() when assigning self to the delegate you are creating a strong reference to the instance of the class. This causes a retention cycle and stops the instance being deinitialised. The answer is to weekly capture the delegate:
URLSession(configuration: config, delegate: self, delegateQueue: .main).dataTask(with: url) { [weak self] (data, response, error) in
print("Received")
}.resume()
In request2() without a delegate this obviously isn't happening.
There are numerous blogs and tutorials about this, so rather than repeating them all I'll let you google for the detail about capture list and strong and weak references :-)
I am trying to connect to the Twitter streaming API endpoint. It looks like URLSession supports streaming via URLSessionStreamTask, however I can't figure out how to use the API. I have not been able to find any sample code either.
I tried testing the following, but there is no network traffic recorded:
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let stream = session.streamTask(withHostName: "https://stream.twitter.com/1.1/statuses/sample.json", port: 22)
stream.startSecureConnection()
stream.readData(ofMinLength: 0, maxLength: 100000, timeout: 60, completionHandler: { (data, bool, error) in
print("bool = \(bool)")
print("error = \(String(describing: error))")
})
stream.resume()
I've also implemented the delegate methods (including URLSessionStreamDelegate), but they do not get called.
It would be really helpful if someone code post a sample of how to open a persistent connection for chunked responses from a streaming endpoint. Also, I am seeking solutions which don't involve third party libraries. A response similar to https://stackoverflow.com/a/9473787/5897233 but updated with the URLSession equivalent would be ideal.
Note: Authorization info was omitted from the sample code above.
Received lots of info courtesy of Quinn "The Eskimo" at Apple.
Alas, you have the wrong end of the stick here. URLSessionStreamTask is for wrangling a naked TCP (or TLS over TCP) connection, without the HTTP framing on top. You can think of it as a high-level equivalent to the BSD Sockets API.
The chunked transfer encoding is part of HTTP, and is thus supported by all of the other URLSession task types (data task, upload task, download task). You don’t need to do anything special to enable this. The chunked transfer encoding is a mandatory part of the HTTP 1.1 standard, and is thus is always enabled.
You do, however, have an option as to how you receive the returned data. If you use the URLSession convenience APIs (dataTask(with:completionHandler:) and so on), URLSession will buffer all the incoming data and then pass it to your completion handler in one large Data value. That’s convenient in many situations but it doesn’t work well with a streamed resource. In that case you need to use the URLSession delegate-based APIs (dataTask(with:) and so on), which will call the urlSession(_:dataTask:didReceive:) session delegate method with chunks of data as they arrive.
As for the specific endpoint I was testing, the following was uncovered:
It seems that the server only enables its streaming response (the chunked transfer encoding) if the client sends it a streaming request. That’s kinda weird, and definitely not required by the HTTP spec.
Fortunately, it is possible to force URLSession to send a streaming request:
Create your task with uploadTask(withStreamedRequest:)
Implement the urlSession(_:task:needNewBodyStream:) delegate method to return an input stream that, when read, returns the request body
Profit!
I’ve attached some test code that shows this in action. In this case it uses a bound pair of streams, passing the input stream to the request (per step 2 above) and holding on to the output stream.
If you want to actually send data as part of the request body you can do so by writing to the output stream.
class NetworkManager : NSObject, URLSessionDataDelegate {
static var shared = NetworkManager()
private var session: URLSession! = nil
override init() {
super.init()
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalCacheData
self.session = URLSession(configuration: config, delegate: self, delegateQueue: .main)
}
private var streamingTask: URLSessionDataTask? = nil
var isStreaming: Bool { return self.streamingTask != nil }
func startStreaming() {
precondition( !self.isStreaming )
let url = URL(string: "ENTER STREAMING URL HERE")!
let request = URLRequest(url: url)
let task = self.session.uploadTask(withStreamedRequest: request)
self.streamingTask = task
task.resume()
}
func stopStreaming() {
guard let task = self.streamingTask else {
return
}
self.streamingTask = nil
task.cancel()
self.closeStream()
}
var outputStream: OutputStream? = nil
private func closeStream() {
if let stream = self.outputStream {
stream.close()
self.outputStream = nil
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: #escaping (InputStream?) -> Void) {
self.closeStream()
var inStream: InputStream? = nil
var outStream: OutputStream? = nil
Stream.getBoundStreams(withBufferSize: 4096, inputStream: &inStream, outputStream: &outStream)
self.outputStream = outStream
completionHandler(inStream)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
NSLog("task data: %#", data as NSData)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error as NSError? {
NSLog("task error: %# / %d", error.domain, error.code)
} else {
NSLog("task complete")
}
}
}
And you can call the networking code from anywhere such as:
class MainViewController : UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if NetworkManager.shared.isStreaming {
NetworkManager.shared.stopStreaming()
} else {
NetworkManager.shared.startStreaming()
}
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
Hope this helps.
So, this is a lot less robust than the example with no explicit task canceling or writing to stream but if you're just YOLO listening to a Server Sent Event stream, this works as of Feb of 2023. It's based on "Use async/await with URLSession" WWDC21 session. That session also has an example for using a custom delegate.
https://developer.apple.com/videos/play/wwdc2021/10095/
func streamReceiverTest(streamURL:URL, session:URLSession) async throws {
let (bytes, response) = try await session.bytes(from:streamURL)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIngError("Not an HTTPResponse")
}
guard httpResponse.statusCode == 200 else {
throw APIngError("Not a success: \(httpResponse.statusCode)")
}
for try await line in bytes.lines {
print(line)
print()
}
}
Inspecting the request with try await streamReceiverTest(streamURL:URL(string:"https://httpbin.org/get")!, session:URLSession.shared) doesn't show that the Accepts header is set, but it seems the API I'm using does need that to offer the stream. Some servers might(?) so I'll include that version as well.
func streamReceiverTestWithManualHeader(streamURL:URL, session:URLSession) async throws {
var request = URLRequest(url:streamURL)
request.setValue("text/event-stream", forHTTPHeaderField:"Accept")
let (bytes, response) = try await session.bytes(for:request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIngError("Not an HTTPResponse")
}
guard httpResponse.statusCode == 200 else {
throw APIngError("Not a success: \(httpResponse.statusCode)")
}
for try await line in bytes.lines {
print(line)
print()
}
}
I'm trying to write default behaviour for a delegate method using a Swift extension as below, but it is never called. Does anyone know why or how to do it the right way?
extension NSURLSessionDelegate {
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
//default behaviour here
}
}
Adding override does not work either.
According to this,
Apple's default implementation looks like:
extension NSURLSessionDelegate {
func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) { }
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) { }
}
My DataTask calls typically look like this:
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
sessionConfiguration.HTTPCookieStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
let session = NSURLSession(configuration: sessionConfiguration)
let requestURL = NSURL(string:"https:www.google.com/blabla")
session.dataTaskWithURL(requestURL!, completionHandler: completion).resume()
Where completion will typically be a Swift closure received via parameter.
I need to implement the URLSession(... didReceiveChallenge ...) function for all nsurlsessiontask implementations throughout my app, but can't set my session's delegate as I need to use the completionHandler (as mentioned in my comment below).
You can extends the NSURLSessionDelegate protocol for adding default implementation, but your NSURLSession objects needs a delegate.
This delegate can only be set using +sessionWithConfiguration:delegate:delegateQueue: (since the delegate property is read only), so your only way to set it is to subclass NSURLSession, override +sessionWithConfiguration: and call the initializer with the delegate property. The issue here is that you have to replace all your NSURLSession objects to MyCustomSessionClass. objects.
I suggest you to create a SessionCreator class which will conforms to NSURLSessionDelegate protocol and will create NSURLSessionobjects. You still have to replace the creation of your objects, but at least the object isn't the delegate of itself.
public class SessionCreator:NSObject,NSURLSessionDelegate {
//MARK: - Singleton method
class var sharedInstance :SessionCreator {
struct Singleton {
static let instance = SessionCreator()
}
return Singleton.instance
}
//MARK: - Public class method
public class func createSessionWithConfiguration (configuration:NSURLSessionConfiguration) -> NSURLSession {
return sharedInstance.createSessionWithConfiguration(configuration)
}
//MARK: - Private methods
private func createSessionWithConfiguration (configuration:NSURLSessionConfiguration) -> NSURLSession {
return NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
//MARK: - NSURLSessionDelegate protocol conformance
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
// Always called since it's the delegate of all NSURLSession created using createSessionWithConfiguration
}
}
// Let create a NSURLSession object :
let session = SessionCreator.createSessionWithConfiguration(NSURLSessionConfiguration())
Experts, I'm a Beginner in IOS 9 / XCODE 7 / Swift 2 Development Kit
I'm trying to create an ios app that simply route to Web Application in HTTPS protocol. Below is my code so far in ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet var myWebView: UIWebView!
/**
* Function to Display the Web Application initial URL
*/
func loadAppURL(){
let siteAddress = "https://domain:8443/path/to/page"
let url = NSURL (string: siteAddress)
let urlRequest = NSURLRequest(URL: url!)
myWebView.loadRequest(urlRequest)
}
override func viewDidLoad() {
super.viewDidLoad()
loadAppURL()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
While Building my App it shows the below Error Message
2015-10-01 01:05:13.879 Web Page Tester[2947:31838]
NSURLSession/NSURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9807)
and if i try building my app instead of "https://domain:8443/path/to/page" with "http://www.apple.com" its works fine.
I can access my web application in Safari and it asks for accepting the security risks. and i accept it and i can access my Application.
Guide me to fix my issues, Thanks in advance.
Finally I fixed it
Xcode will reject un-trusted self signed certificates from servers by default.
we can override this Using NSURLConnection and can communicate with a self-signed server, since we have the ability to control the authentication through the additional delegate methods which are not available to a UIWebView. So using connection:didReceiveAuthenticationChallenge we can authenticate against the self signed server.
References
NSURLAuthenticationChallenge Docs , #Lilo Lu's Question
I Resolved My Issue in below steps
Step 1 : Defined a NSURLConnection in viewDidLoad() method of my viewController.swift as follows
override func viewDidLoad() {
super.viewDidLoad()
let siteAddress = "https://domain:8443/path/to/page"
let url = NSURL (string: siteAddress)
let urlRequest = NSURLRequest(URL: url!)
let urlConnection:NSURLConnection = NSURLConnection(request: request, delegate: self)!
myWebView.loadRequest(urlRequest)
}
Step 2 : used NSURLConnection delegate methods
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool{
print("canAuthenticateAgainstProtectionSpace method Returning True")
return true
}
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge){
print("did autherntcationchallenge = \(challenge.protectionSpace.authenticationMethod)")
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
print("send credential Server Trust")
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
}else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic{
print("send credential HTTP Basic")
let defaultCredentials: NSURLCredential = NSURLCredential(user: "username", password: "password", persistence:NSURLCredentialPersistence.ForSession)
challenge.sender!.useCredential(defaultCredentials, forAuthenticationChallenge: challenge)
}else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM{
print("send credential NTLM")
} else{
challenge.sender!.performDefaultHandlingForAuthenticationChallenge!(challenge)
}
}
and that worked !!
You can add the following to your plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
In Swift 3.
Step 1. Add NSURLConnectionDelegate to your viewcontroller, to overwrite methods.
class ViewController: UIViewController, NSURLConnectionDelegate {
Step 2. Override viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
let siteAddress = "https://mysiteaddress"
let url = URL(string: siteAddress)
let urlRequest = URLRequest(url: url!)
let urlConnection:NSURLConnection = NSURLConnection(request: urlRequest, delegate: self)!
webView.loadRequest(urlRequest)
}
Step 3 Overwrite canAuthenticateAgainstProtectionSpace and didReceive challenge
func connection(_ connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: URLProtectionSpace) -> Bool {
print("\ncanAuthenticateAgainstProtectionSpace method Returning True\n")
return true
}
func connection(_ connection: NSURLConnection, didReceive challenge: URLAuthenticationChallenge) {
print("did autherntcationchallenge = \(challenge.protectionSpace.authenticationMethod)")
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
print("\nsend credential Server Trust\n")
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
challenge.sender!.use(credential, for: challenge)
}else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic{
print("send credential HTTP Basic")
let defaultCredentials: URLCredential = URLCredential(user: "user", password: "password", persistence:URLCredential.Persistence.forSession)
challenge.sender!.use(defaultCredentials, for: challenge)
}else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodNTLM{
print("\nsend credential NTLM\n")
} else{
challenge.sender!.performDefaultHandling!(for: challenge)
}
}
Thanks Navas Basheer for the original solution! Saved me a ton of time
1- Create category "NSURLRequestCategory" -> after import this category to your bridge file created by xcode (don't forget to let xCode create one if you dont haven't) and put this code :
#implementation NSURLRequest (NSURLRequestCategory)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
{
return YES;
}
#end
Create your load request UIWebView normally :
webView.delegate = self
let myURL = URL(string: Constants.URL_DOMAINE)
let request = URLRequest(url: myURL!)
webView.loadRequest(request)
Enjoyyy :D
edit Info.plist, Add:
App Transport Security Settings
Allow Arbitrary Loads , value is YES
it's work for me, XCode 7.3
I'm not sure what the deal is here, but the function:
class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool
is never called within my NSURLProtocol subclass. I've even seen cases of the cache being used (verified by using a network proxy and seeing no calls being made) but this method just never gets invoked. I'm at a loss for why this is.
The problem I'm trying to solve is that I have requests that I'd like to cache data for, but these requests have a signature parameter that's different for each one (kind of like a nonce). This makes it so the cache keys are not the same despite the data being equivalent.
To go into explicit detail:
I fire a request with a custom signature (like this:
www.example.com?param1=1¶m2=2&signature=1abcdefabc312093)
The request comes back with an Etag
The Etag is supposed to be managed by the NSURLCache but since it thinks that a different request (www.example.com?param1=1¶m2=2&signature=1abdabcda3359809823) is being made it doesn't bother.
I thought that using NSURLProtocol would solve all my problems since Apple's docs say:
class func requestIsCacheEquivalent(_ aRequest: NSURLRequest,
toRequest bRequest: NSURLRequest) -> Bool
YES if aRequest and bRequest are equivalent for cache purposes, NO
otherwise. Requests are considered equivalent for cache purposes if
and only if they would be handled by the same protocol and that
protocol declares them equivalent after performing
implementation-specific checks.
Sadly, the function is never called. I don't know what the problem could be...
class WWURLProtocol : NSURLProtocol, NSURLSessionDataDelegate {
var dataTask: NSURLSessionDataTask?
var session: NSURLSession!
var trueRequest: NSURLRequest!
private lazy var netOpsQueue: NSOperationQueue! = NSOperationQueue()
private lazy var delegateOpsQueue: NSOperationQueue! = NSOperationQueue()
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
println("can init with request called")
return true
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
println("canonical request for request called")
return request
}
override class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool {
// never ever called?!?
let cacheKeyA = a.allHTTPHeaderFields?["CacheKey"] as? String
let cacheKeyB = b.allHTTPHeaderFields?["CacheKey"] as? String
println("request is cache equivalent? \(cacheKeyA) == \(cacheKeyB)")
return cacheKeyA == cacheKeyB
}
override func startLoading() {
println("start loading")
let sharedSession = NSURLSession.sharedSession()
let config = sharedSession.configuration
config.URLCache = NSURLCache.sharedURLCache()
self.session = NSURLSession(configuration: config, delegate: self, delegateQueue: self.delegateOpsQueue)
dataTask = session.dataTaskWithRequest(request, nil)
dataTask?.resume()
}
override func stopLoading() {
println("stop loading")
dataTask?.cancel()
}
//SessionDelegate
func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
println("did become invalid with error")
client?.URLProtocol(self, didFailWithError: error!)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
println("did complete with error")
if error == nil {
client?.URLProtocolDidFinishLoading(self)
} else {
client?.URLProtocol(self, didFailWithError: error!)
}
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
println("did receive response")
client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .Allowed)
completionHandler(.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
println("did receive data called")
client?.URLProtocol(self, didLoadData: data)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse!) -> Void) {
println("will cache response called")
client?.URLProtocol(self, cachedResponseIsValid: proposedResponse)
completionHandler(proposedResponse)
}
I registered the protocol in my app delegate as follows:
NSURLProtocol.registerClass(WWURLProtocol.self)
I trigger the protocol as follows:
#IBAction func requestData(endpointString: String) {
let url = NSURL(string: endpointString)
let request = NSMutableURLRequest(URL: url!)
var cacheKey = endpointString
request.setValue("\(endpointString)", forHTTPHeaderField: "CacheKey")
request.cachePolicy = .UseProtocolCachePolicy
NSURLConnection.sendAsynchronousRequest(request, queue: netOpsQueue) { (response, data, error) -> Void in
if data != nil {
println("succeeded with data:\(NSString(data: data, encoding: NSUTF8StringEncoding)))")
}
}
}
I think that in practice, the loading system just uses the canonicalized URL for cache purposes, and does a straight string comparison. I'm not certain, though. Try appending your nonce when you canonicalize it, in some form that is easily removable/detectable (in case it is already there).
Your code seems all right.You just follow documents of Apple about URLProtocol.You could try to use URLSession for NSURLConnection is deprecated in newer iOS version.Good luck.