Swift - downcast NSURLResponse to NSHTTPURLResponse in order to get response code - ios

I'm building rest queries in SWIFT using NSURLRequest
var request : NSURLRequest = NSURLRequest(URL: url)
var connection : NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
My question is how to do i get response code out of the response that is returned:
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
//...
}
According to Apple: NSHTTPURLResponse which is a subclass of NSURLResponse has a status code but I'm not sure how to downcast my response object so i can see the response code.
This doesn't seem to cut it:
println((NSHTTPURLResponse)response.statusCode)
Thanks

Use an optional cast (as?) with optional binding (if let):
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
if let httpResponse = response as? NSHTTPURLResponse {
println(httpResponse.statusCode)
} else {
assertionFailure("unexpected response")
}
}
or as a one-liner
let statusCode = (response as? NSHTTPURLResponse)?.statusCode ?? -1
where the status code would be set to -1 if the response is not an HTTP response
(which should not happen for an HTTP request).

Related

Connection retry in NSURLSession

I want to implement the connection retry in NSURLSession. Is there any parameter we need to set to achieve this like 'timeoutIntervalForRequest' and NSURLSession takes the responsibility to retry the connection.
If there is no any parameter for this, how can we achieve this?
My current code is as follows:
func isHostConnected(jsonString:NSDictionary) -> NSDictionary
{
let request = NSMutableURLRequest(URL: NSURL(string: "http://***.*.*.**:****/")!)
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(jsonString, options: [])
} catch {
//error = error1
request.HTTPBody = nil
}
request.timeoutInterval = 4.0 //(number as! NSTimeInterval)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("gzip", forHTTPHeaderField: "Accept-encoding")
var JSONdata: AnyObject = ["" : ""] as Dictionary<String, String>
print(JSONdata)
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var responseCode = -1
let group = dispatch_group_create()
dispatch_group_enter(group)
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let httpResponse = response as? NSHTTPURLResponse {
responseCode = httpResponse.statusCode
let JSONresdata: AnyObject = (try! NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers))
JSONdata = JSONresdata as! NSDictionary
}
dispatch_group_leave(group)
}).resume()
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
print("responseCode == 200: \(responseCode)")
return (JSONdata) as! NSDictionary
}
When response code is not 200 then this function should retry the connection again. Can I do the same.
Please check the answer of this link
func someMethodWithRetryCounter(retryCounter: Int) {
if retryCounter == 0 {
return
}
retryCounter--
var request: NSMutableURLRequest = NSMutableURLRequest.requestWithURL(NSURL.URLWithString(self.baseUrl.stringByAppendingString(path)))
(self) weakSelf = self
var dataTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {(data: NSData, response: NSURLResponse, error: NSErrorPointer) in var httpResponse: NSHTTPURLResponse = response
var responseStatusCode: UInt = httpResponse.statusCode()
if responseStatusCode != 200 {
weakSelf.someMethodWithRetryCounter(retryCounter)
}
else {
completionBlock(results["result"][symbol])
}
})
dataTask.resume()
}
You can also use the following default iOS function. It provide a replacement request body stream if the task needs to resend a request that has a body stream because of an authentication challenge or other recoverable server error.
Check these Link/Link for reference
func URLSession(_ session: NSURLSession,
task task: NSURLSessionTask,
needNewBodyStream completionHandler: (NSInputStream?) -> Void)
Hope this might be helpful.

Extra argument 'error' in call - Unable to build my Xcode project

import Foundation
class NetworkOperation {
lazy var config: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
lazy var session: NSURLSession = NSURLSession(configuration: self.config)
let queryURL: NSURL
typealias JSONDictionaryCompletion = ([String: AnyObject]? -> Void)
init(url: NSURL) {
self.queryURL = url
}
func downloadJSONFromURL(completion: JSONDictionaryCompletion) {
let request = NSURLRequest(URL: queryURL)
let dataTask = session.dataTaskWithRequest(request) {
(let data, let response, let error) in
// 1. Check HTTP response for successful GET request
if let httpResponse = response as? NSHTTPURLResponse {
switch httpResponse.statusCode {
case 200:
// 2. Create JSON object with data
let jsonDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
completion(jsonDictionary)
default:
print("GET request not successful. HTTP status code: \(httpResponse.statusCode)")
}
} else {
print("Error: Not a valid HTTP response")
}
}
dataTask.resume()
}
}
In the 'Create JSON object with data' step, I keep receiving the "extra argument 'error' in call". What is happening? I am unable to find documentation to help me further in this.
You can do it by this way.
do{
var jsonDictionary = try NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers)
//completion(jsonDictionary)
}catch{
// report error
}
at the top of step 2: creating json....
add this line:
var err: NSError?
// 1. Check HTTP response for successful GET request
if let httpResponse = response as? NSHTTPURLResponse {
switch httpResponse.statusCode {
case 200:
// 2. Create JSON object with data
let jsonDictionary = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [String: AnyObject]
completion(jsonDictionary)
default:
println("GET request not successful. HTTP status code: \(httpResponse.statusCode)")
}
} else {
println("Error: Not a valid HTTP response")
}
}
dataTask.resume()
}
}
Finally figured it out! Thank you for your input everyone!

NSURLSession URL Response is not cached when larger than a few kilobytes

I am pretty new to programming in IOS with Cocoa and I am using Swift. I fetch data from a JSON API using an NSURLSession data session with custom delegate, not closures. The reason for using custom delegate is that I have to do basic authentication and I also inject a custom cache-control header to control caching behavior (my API doesn’t include any caching related headers in the response at all).
All this works perfectly but only for requests for which the URLSession:dataTask:didReceiveData: method is called only once. As soon as I get larger responses (some 20-30kBytes) that call the didReceivedData method several times, the URLSession:dataTask:willCacheResponse:completionHandler: method doesn’t get called at all, and therefore my response doesn’t get cached. Re-issuing the same request within 5 minutes will issue a request to the server again, which doesn’t happen for requests whose responses only call didReceiveData one single time. The URLSession:task:didCompleteWithError: method is correctly called and proceeded in all cases.
The documentation of the URLSession:dataTask:willCacheResponse:completionHandler: method (https://developer.apple.com/library/IOs/documentation/Foundation/Reference/NSURLSessionDataDelegate_protocol/index.html#//apple_ref/occ/intfm/NSURLSessionDataDelegate/URLSession:dataTask:willCacheResponse:completionHandler:) says this method is only called if the NSURLProtocol handling the request decides to do so, but I don’t really understand what to do to make this happen.
Any feedback and ideas are very welcome!
This is the code that issues the HTTP request:
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.URLCache = NSURLCache.sharedURLCache()
//config.URLCache = NSURLCache(memoryCapacity: 512000000, diskCapacity: 1000000000, diskPath: "urlCache")
let urlString = apiUrlForFilter(filter, withMode: mode, withLimit: limit, withOffset: offset)
let url = NSURL(string: urlString)
var policy: NSURLRequestCachePolicy?
if ignoreCache == true {
policy = .ReloadIgnoringLocalCacheData
} else {
policy = .UseProtocolCachePolicy
}
let request = NSURLRequest(URL: url!, cachePolicy: policy!, timeoutInterval: 20)
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithRequest(request)
task.resume()
I have the following delegate functions implemented:
URLSession:didReceiveChallenge:completionHandler: to handle SSL certificate trust,
URLSession:task:didReceiveChallenge:completionHandler: to handle basic authentication
URLSession:dataTask:didReceiveResponse:completionHandler: to read some specific headers and save them as instance variables
additionally, the important ones with code:
URLSession:dataTask:didReceiveData: to accumulate data as it arrives for larger HTTP request responses:
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if receivedData == nil {
receivedData = NSMutableData()
}
receivedData!.appendData(data)
println("did receive data: \(receivedData!.length) bytes")
}
URLSession:dataTask:willCacheResponse:completionHandler: to inject my own Cache-Control header:
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse!) -> Void) {
println("willCacheResponse was called")
let response: NSURLResponse = proposedResponse.response
let httpResponse = response as NSHTTPURLResponse
var headers = httpResponse.allHeaderFields
var modifiedHeaders = headers
modifiedHeaders.updateValue("max-age=300", forKey: "Cache-Control")
let modifiedResponse = NSHTTPURLResponse(URL: httpResponse.URL!, statusCode: httpResponse.statusCode, HTTPVersion: "HTTP/1.1", headerFields: modifiedHeaders)
let cachedResponse = NSCachedURLResponse(response: modifiedResponse!, data: proposedResponse.data, userInfo: proposedResponse.userInfo, storagePolicy: proposedResponse.storagePolicy)
completionHandler(cachedResponse)
}
URLSession:task:didCompleteWithError: to check the complete response for errors and call a callback closure this class gets by initialization to further proceed the result data:
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
session.finishTasksAndInvalidate()
if error != nil {
var string: String?
if errorString != nil {
string = errorString
} else {
string = Helpers.NSURLErrorDomainErrorForCode(error!.code)
}
errorCallback(string!)
return
}
if receivedData == nil {
errorCallback("the query returned an empty result")
return
}
var jsonError: NSError?
let results: AnyObject! = NSJSONSerialization.JSONObjectWithData(receivedData!, options: NSJSONReadingOptions.AllowFragments, error: nil)
if results == nil {
errorCallback("the data returned was not valid JSON")
return
}
let jsonParsed = JSONValue.fromObject(results)
if let parsedAPIError = jsonParsed!["error"]?.string {
errorCallback("API error: \(parsedAPIError)")
return
}
callback(jsonParsed!, self.serverTime!)
}

URL File Size With NSURLConnection - Swift

i am trying to get a file size from url before downloading
here is the obj-c code
NSURL *URL = [NSURL URLWithString:"ExampleURL"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: nil];
long long size = [response expectedContentLength];
and here is Swift Code
var url:NSURL = NSURL(string: "ExmapleURL")
var request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "HEAD"
var response = NSHTTPURLResponse()
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response , error: nil)
but i have error here
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response , error: nil)
'NSHTTPURLResponse' is not identical to 'NSURLResponse?'
did i miss something in swift here ?
The response parameter has the type
AutoreleasingUnsafeMutablePointer<NSURLResponse?>
which means that you can pass the address of an optional NSURLResponse as argument:
var response : NSURLResponse?
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response , error: nil)
You can then conditionally cast the returned response to a NSHTTPURLResponse:
if let httpResponse = response as? NSHTTPURLResponse {
println(httpResponse.expectedContentLength)
}
Note that you should check the return value of sendSynchronousRequest(), which
is nil if no connection could be made.
It is also recommended to call this
method only from a separate thread (or use sendAsynchronousRequest() instead)
because it can take a while to make a connection
– in particular when using a cellular network – and the main thread would be
blocked otherwise.
Swift 4 solution:
func fetchContentLength(for url: URL, completionHandler: #escaping (_ contentLength: Int64?) -> ()) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil, let response = response as? HTTPURLResponse, let contentLength = response.allHeaderFields["Content-Length"] as? String else {
completionHandler(nil)
return
}
completionHandler(Int64(contentLength))
}
task.resume()
}
// Usage:
let url = URL(string: "https://s3.amazonaws.com/x265.org/video/Tears_400_x265.mp4")!
fetchContentLength(for: url, completionHandler: { contentLength in
print(contentLength ?? 0)
})

NSURLResponse does not have a member named allHeaderFields

I'm making a POST request to an API and I get the response successfully in Swift. Below is my code.
private func getData(url: NSURL) {
let config: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session: NSURLSession = NSURLSession(configuration: config)
let dataTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {(data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if error {
println("Error Occurred: \(error.localizedDescription)")
} else {
println("\(response.allHeaderFields)") // Error
}
})
dataTask.resume()
}
I'm trying to dump the header fields using allHeaderFields but I get an error saying NSURLResponse does not have a member named allHeaderFields. But it does have it!
There must be something wrong with the syntax or the way I'm calling it. Can anyone please tell me how to correct this?
Thank you.
Elaborating on what Yogesh said...!
Try to cast the NSURLRespones into a NSHTTPURLResponse using "as", because I'm betting the NSURLResponse is actually a NSHTTPURLResponse, or I'm betting that is possible.
Here is what I mean:
private func getData(url: NSURL) {
let config: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session: NSURLSession = NSURLSession(configuration: config)
let dataTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {(data: NSData!, urlResponse: NSURLResponse!, error: NSError!) -> Void in
if let httpUrlResponse = urlResponse as? NSHTTPURLResponse
{
if error {
println("Error Occurred: \(error.localizedDescription)")
} else {
println("\(httpUrlResponse.allHeaderFields)") // Error
}
}
})
dataTask.resume()
}
From the link you have provided Link
The NSHTTPURLResponse class is a subclass of NSURLResponse that provides methods for accessing information specific to HTTP protocol responses
And allHeaderFields is method of NSHTTPURLResponse class not NSURLResponse class. So you have to use NSHTTPURLResponse instead of NSURLResponse class.
if navigationResponse.response is HTTPURLResponse {
let response = navigationResponse.response as! HTTPURLResponse
print(response.allHeaderFields) // all headers
}
Swift 3 and higher solution
Here is a solution to handle the data tasks in Swift 3 and higher.
let urlPath: String = "http://www.google.de"
guard let url: URL = URL(string: urlPath) else { return }
let request = URLRequest(url: url)
let response: URLResponse?
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
if let httpResponse = response as? HTTPURLResponse {
print("error \(httpResponse.statusCode)")
}
}.resume()

Resources