can anyone tell me what all URLSession delegate methods should me implemented along with an example in swift 4.
I DO NOT WANT TO USE COMPLETION HANDLER.
My app is in objective c, and i am converting it in swift.
earlier it uses NSURLConnection which is now deprecated.
So it's little confusing for me to use URLSession.
Thanks!
EDIT:
I am using something like
let defaultSession = URLSession(configuration: defaultSessionConfiguration, delegate: self, delegateQueue: nil)
let sessionTask = defaultSession.dataTask(with: requests)
sessionTask.resume()
Now i want to implement methods which will handle senarios when it receives response, data or error.
I am looking for equivalent of
func connection(_ connection: NSURLConnection, didReceive data: Data){}
func connection(_ connection: NSURLConnection, didFailWithError error: Error){}
func connection(_ connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: URLProtectionSpace) -> Bool {
return protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
}
func connection(_ connection: NSURLConnection, didReceive challenge: URLAuthenticationChallenge) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let aTrust = challenge.protectionSpace.serverTrust {
challenge.sender?.use(URLCredential(trust: aTrust), for: challenge)
}
}
challenge.sender?.continueWithoutCredential(for: challenge)
}
Related
I'm using a custom NSURLProtocol in order to be able to save the username and password from a form that is loaded in a webView and automatically log the user in.
My issue is that I can get the data, but I can't complete the action to login the user as the webpage uses some JS to listen to the response and complete the form action.
Is there a way to do it?
Here's my current CustomURLProtocol class:
class CustomURLProtocol: NSURLProtocol {
var connection: NSURLConnection!
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
if NSURLProtocol.propertyForKey("customProtocolKey", inRequest: request) != nil {
return false
}
return true
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
return request
}
override func startLoading() {
let newRequest = request.mutableCopy() as! NSMutableURLRequest
NSURLProtocol.setProperty(true, forKey: "customProtocolKey", inRequest: newRequest)
// Look for user credentials
if request.URL?.description == "https://www.website.com/Account/Login" {
if let data = request.HTTPBody {
let dataString: String = NSString(data: data, encoding: NSASCIIStringEncoding) as! String
// Code for reading user credentials goes here
}
}
connection = NSURLConnection(request: newRequest, delegate: self)
}
override func stopLoading() {
if connection != nil {
connection.cancel()
}
connection = nil
}
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
client!.URLProtocol(self, didLoadData: data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
client!.URLProtocolDidFinishLoading(self)
}
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
client!.URLProtocol(self, didFailWithError: error)
}
}
Is there something I'm missing or is wrong in my logic?
Thanks in advance
So if I understand correctly, you're intercepting the call just to preserve the credentials so you can log the user in automatically later? If you only care about the login request you could move the url check to canInitWithRequest, but that's a side issue.
Using a custom NSURLProtocol correctly shouldn't affect the response, so the web page should behave normally. I notice that you're using NSURLConnection, which is deprecated, so you might want to look at using NSURLSession but again that's a side issue.
Have you stepped through the code to see where it's failing? Are your connection delegate methods being called? Is "webViewDidFinishLoad" being called on the web view?
Or are you not talking about the initial form submission, but subsequent ones when you're trying to log in the user with the stored credentials?
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.
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())
Recently I read the source code of Alamofire, and I am really confused about How could Alamofire guarantee the response method would be called in correctly order. I hope someone(maybe matt lol) could help me out.
Example, an easy GET Request like this
Alamofire.request(.GET, "https://api.github.com/users/octocat/received_events")
After I analysed the work flow of it, I posted my understanding
Create the request and underlying NSURLSession.
public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
return Manager.sharedInstance.request(method, URLString, parameters: parameters, encoding: encoding)
}
This method would create a request: Request object, which would contain the underlying NSURLSessionDataTask object. Manager.sharedInstance has already set up a NSURLSession and set itself as that session's delegate. The Manager.sharedInstance object would save a customized object request.delegate in its own property delegate
After those objects were created, Alamofire would send this request immediately.
public func request(URLRequest: URLRequestConvertible) -> Request {
var dataTask: NSURLSessionDataTask?
dispatch_sync(queue) {
dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest)
}
let request = Request(session: session, task: dataTask!)
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
Since Manager.sharedInstance set itself as the underlying NSURLSession's delegate, when data received, the delegate methods would be called
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if dataTaskDidReceiveData != nil {
dataTaskDidReceiveData!(session, dataTask, data)
} else if let delegate = self[dataTask] as? Request.DataTaskDelegate {
delegate.URLSession(session, dataTask: dataTask, didReceiveData: data)
}
}
If a user want to get the response and do something related, he would use following public API
// Here the request is a Request object
self.request?.responseString { (request, response, body, error) in
// Something do with the response
}
let's see what the responseString(_: completionHandler:) method does
public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
delegate.queue.addOperationWithBlock {
let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data)
dispatch_async(queue ?? dispatch_get_main_queue()) {
completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError)
}
}
return self
}
My question is how 5 could be guaranteed to happen after 3, so the user could get all the response not part of it, because self.response at this time would be fully loaded.
Is it because of NSURLSession's background processing occurs on the same queue -- delegate.queue, which is created like this in Alamofire:
//class Request.TaskDelegate: NSObject, NSURLSessionTaskDelegate
self.queue = {
let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.suspended = true
if operationQueue.respondsToSelector("qualityOfService") {
operationQueue.qualityOfService = NSQualityOfService.Utility
}
return operationQueue
}()
Is my understanding correct, how does that happen? It might be require some understanding about RunLoop and NSURLSession's thread mechanism, if you could point out where I could refer to, thank you as well.
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.