I download data from Internet in this way using Swift:
let postEndpoint: String = "http://webisitetest.com"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) in
// this is where the completion handler code goes
print(response)
print(error)
})
task.resume()
Because I download also images and download can during various seconds (depends connection), if during download the user loses connection, how I do handle this situation?
I want to show him a message for example "Connection lost, download cancelled, try again", but how I do catch this event?
Check for error presence in your completion handler:
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (maybeData: NSData?, maybeResponse: NSURLResponse?, maybeError: NSError?) in
// check for error
guard maybeError = nil else {
print(maybeError!)
}
// otherwise process the request
print(maybeResponse)
})
Any failure can be handled in the completion block. You can also specify the seconds before timeout for request and resource in the configuration.
- (NSURLSessionConfiguration*) requestConfigurations
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
configuration.timeoutIntervalForRequest = 120;
configuration.timeoutIntervalForResource = 980.0;
return configuration;
}
Error codes from the error can help you distinguish the type of failure.
Related
I am trying to make dispatchgroup work in my code
let dispatchQueue:DispatchQueue = DispatchQueue(label: "com.dispatchgroup", attributes: .concurrent, target: .main)
var dispatchGroup:DispatchGroup = DispatchGroup()
func renewLoginIfRequired()->String{
self.dispatchGroup.enter()
dispatchQueue.async(group:dispatchGroup){
self.authorizeApplication(completionHandler: {
self.dispatchGroup.leave()
})
}
}
self.dispatchGroup.wait() // Stops execution here
return "Login Success"
}
Above code stops execution at self.dispatchGroup.wait().
I have tried the same code without dispatchQueue.async(group:dispatchGroup) around self.authorizeApplication as well with no luck
I am not sure what am i doing wrong. One thing to mention here is that self.authorizeApplication will make an async web service request within that function
Edit 1:
To elaborate the problem even more. I will be returning a session token (String) from this method.
Here is the function which is calling this
func processPOSTRequest(_ urlString : String, isAnAuthReq:Bool = false, requestObject: Data?, onSuccess: #escaping (NSDictionary?, NSError?) -> Void, onFailure: #escaping (Data?, NSError?) -> Void){
let manager = AFHTTPSessionManager()
let url = URL(string: urlString);
var request = URLRequest(url: url!);
if !isAnAuthReq{
let token = self.renewLoginIfRequired() //-- Get new token before processing a webservice request
request.setValue(token, forHTTPHeaderField: "Authorization")
}
print("processing URl : "+urlString)
request.httpMethod="POST";
request.httpBody = requestObject
request.setValue("application/json; charset=utf-8", forHTTPHeaderField:"Content-Type")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField:"Accept")
let task = manager.dataTask(with: request, uploadProgress: nil, downloadProgress: nil, completionHandler:{ data, response, error in
if(error == nil){
if let responseCode = (data as? HTTPURLResponse)?.statusCode{
if responseCode != 200 || !((response as? [String: Any])?["success"] as? Bool)!{
let errorResponse = NSError()
print("Response received for URL: "+urlString)
onFailure(nil, errorResponse.addItemsToUserInfo(newUserInfo: ["errorCode":String(responseCode)]))
}
else{
onSuccess(response as! NSDictionary?, nil)
}
}
}
else{
onFailure(nil, error as NSError?)
}
})
task.resume()
}
If i use Notify or closure. How can i do that? I have tried both of them
I don't know what is under the hood of the authorizeApplication request, but it's likely that its callback performs in the main thread, that is, in the main queue. So you enter, leave and wait in the same queue, therefore you can't reach the self.dispatchGroup.leave() after you've invoked self.dispatchGroup.wait().
To handle that you need to redesign the async request and call self.dispatchGroup.leave() in a background queue.
I'm trying to load a JSON file from a web server. Here's how I kick off the request:
let url:NSURL? = NSURL(string: lookupUrlFragment + query)
// Check if an actual url object was created
if let actualUrl = url {
// Create a default NSURLSessionConfiguration
let sessionConfig:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
// Create a default session
let session:NSURLSession = NSURLSession(configuration: sessionConfig)
session.dataTaskWithURL(actualUrl, completionHandler: {
(data:NSData?, response:NSURLResponse?, error:NSError?) in
NSLog("Got data = \(data)")
NSLog("Got response = \(response)")
NSLog("Got error = \(error)")
self.searchResults = data
self.delegate?.searchResultsAreReady()
})
}
I've stepped through this code with the debugger. When it gets to the invocation of dataTaskWithURL() the value of actual Url is correct. If I hit it from a web browser, I get the JSON file. But the completion handler never gets called. It never stops at a break point I set in the completion handler, and no output appears in the debugger log.
I've tried this with the completion handler in a separate function instead of a closure, but the behavior is the same.
Can anyone tell me why my completion handler isn't getting called?
You forgot to call resume().
let session:NSURLSession = NSURLSession(configuration: sessionConfig)
let task = session.dataTaskWithURL(actualUrl, completionHandler: {
(data:NSData?, response:NSURLResponse?, error:NSError?) in
NSLog("Got data = \(data)")
NSLog("Got response = \(response)")
NSLog("Got error = \(error)")
self.searchResults = data
self.delegate?.searchResultsAreReady()
})
task.resume() // you miss this
You are never starting the task. Try this:
let url:NSURL? = NSURL(string: lookupUrlFragment + query)
// Check if an actual url object was created
if let actualUrl = url {
// Create a default NSURLSessionConfiguration
let sessionConfig:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
// Create a default session
let session:NSURLSession = NSURLSession(configuration: sessionConfig)
let task = session.dataTaskWithURL(actualUrl, completionHandler: {
(data:NSData?, response:NSURLResponse?, error:NSError?) in
NSLog("Got data = \(data)")
NSLog("Got response = \(response)")
NSLog("Got error = \(error)")
self.searchResults = data
self.delegate?.searchResultsAreReady()
})
task.resume()
}
In general, it is necessary to implement a class for the network. This is a class that will take a URL, and to give data. All this is done in order not to score an extra logic controllers. I encountered such a problem that when you first create a View, the data do not come. That's Network Class:
private static var dataTask: NSURLSessionDataTask?
private static var dataJSON: NSData?
private static var sessionConfig: NSURLSessionConfiguration = {
var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.allowsCellularAccess = false
configuration.HTTPMaximumConnectionsPerHost = 2
configuration.HTTPAdditionalHeaders = ["Accept": "application/json"]
configuration.timeoutIntervalForRequest = 30.0
configuration.timeoutIntervalForResource = 60.0
return configuration
}()
static func getListObjectsBy(url: String?) -> NSData? {
let session = NSURLSession(configuration: sessionConfig)
log.debug("DataTask start")
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { (data, response, error) in
log.debug("if error = error")
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
log.debug("if httpResponse")
if httpResponse.statusCode == 200 {
dataJSON = data
} else {
print("Bad request")
}
}
}
dataTask?.resume()
log.debug("DataTask Resume")
return dataJSON
}
Method viewDidLoad in my main controller:
let response = Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5")
print(String(response))
My log say me, that data return nil. Notes, i'm switch between controllers with help SWRevealViewController. When reloading the main view controller, the data is returned. What me do?
enter image description here
You seem to be misunderstanding that this is an asynchronous call.
static func getListObjectsBy(url: String?) -> NSData? {
let session = NSURLSession(configuration: sessionConfig)
log.debug("DataTask start")
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { (data, response, error) in
// Everything in this block is happening on a separate thread.
log.debug("if error = error")
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
log.debug("if httpResponse")
if httpResponse.statusCode == 200 {
// this won't happen until the data comes back from the remote call.
dataJSON = data
} else {
print("Bad request")
}
}
}
// This code here does not wait for the response from the remote.
// The call to the remote is sent then this code
// is immediately executed WITHOUT WAITING
dataTask?.resume()
log.debug("DataTask Resume")
// dataJSON will be nil until the remote answers.
return dataJSON
}
When you do this:
let response = Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5")
print(String(response))
The remote has not answered yet so you will get nil.
Your next question might be "what do I do about this?". The answer isn't clear without knowing everything else that you are doing.
Threads
Multiple threads of execution is like two programs running at the same time. Think of 2 people working on two different tasks at the same time. In order to keep the interface very responsive, iOS uses one thread of execution for updating the screen. If a process has to run that take a long time, we would not want the screen to wait until that is done. Let's say you have to fetch data from some remote system and that remote system is slow, your device would sit there frozen until the response came back. To avoid this, activities like calls to remote systems are done in another thread. The request is sent to the operating system itself and the operating system is told to call back when the operation is done.
This is what is happening here.
Sets up the request to send to the operating system.
dataTask = session.dataTaskWithURL(NSURL(string: url!)!)
Tells the operating system to start doing the work.
dataTask?.resume()
This block is the callback AKA the closure. iOS will run this code when the remote call is done.
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) {
// Closure starts here
// Gets called when the remote has sent a response.
(data, response, error) in
// Everything in this block is happening on a separate thread.
log.debug("if error = error")
etc
}
This means you must wait until the response has come back before printing your output. You can use a closure in your function to do this.
public typealias CompletionHandler = (data: NSData?, error: NSError?) -> Void
static func getListObjectsBy(url: String?, completion: CompletionHandler) {
let session = NSURLSession(configuration: sessionConfig)
log.debug("DataTask start")
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) {
(data, response, error) in
// Everything in this block is happening on a separate thread.
log.debug("if error = error")
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
log.debug("if httpResponse")
if httpResponse.statusCode == 200 {
// this won't happen until the data comes back from the remote call.
} else {
print("Bad request")
}
}
// Call your closure
completion(data, error)
}
// This code here does not wait for the response from the remote.
// The call to the remote is sent then this code
// is immediately executed WITHOUT WAITING
dataTask?.resume()
log.debug("DataTask Resume")
}
In your calling code you would do this:
Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5") {
(data, error) in
if let data == data {
print(data)
}
}
On certain websites the below method hangs indefinitely, I'd like to cancel the request when its taking too long, I thought that timeoutIntervalForRequest and timeoutIntervalForResource would control this but setting either of them doesn't seem to have any effect.
This is an example website that the request hangs indefinitely on: http://www.samtoft.co.uk/showpic.php?id=542&order=&search=#header
// fetch data from URL with NSURLSession
class func getDataFromServerWithSuccess(myURL: String, noRedirect: Bool, callback: Result<String> -> Void) {
var loadDataTask: NSURLSessionDataTask? = nil
let sessionConfig: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
sessionConfig.timeoutIntervalForRequest = 10.0
sessionConfig.timeoutIntervalForResource = 10.0
var myDelegate: MySession? = nil
if noRedirect {
myDelegate = MySession()
}
let session = NSURLSession(configuration: sessionConfig, delegate: myDelegate, delegateQueue: nil)
loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if let checkedData = data {
let success = Result.Success(NSString(data: checkedData, encoding: NSASCIIStringEncoding) as! String)
callback(success)
} else {
let redirectError = NetworkError.FailedUrl("\(myURL) + \(error)")
if let request = loadDataTask?.currentRequest {
guard let urlExtension = request.URL?.pathExtension else {return}
guard let domain = request.URL?.host else {return}
guard let finalURLAsString = request.URL?.description else {return}
let failure = Result.Failure("\(finalURLAsString) + \(redirectError)")
callback(failure)
}
}
}
loadDataTask!.resume()
}
EDIT: for anyone who is having the same issue I was, the problem was that I was accounting for the success case and the error case, but not for when the request returned no response, so I added the below:
if response == nil {
print("NO RESPONSE \(myURL)")
let noResponse = Result.NoResponse("NO RESPONSE")
callback(noResponse)
}
I have extracted your code as
func getDataFromServerWithSuccess(myURL: String) {
var loadDataTask: NSURLSessionDataTask? = nil
let sessionConfig: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
//sessionConfig.timeoutIntervalForRequest = 11.0
sessionConfig.timeoutIntervalForResource = 11.0
let session = NSURLSession(configuration: sessionConfig)
loadDataTask = session.dataTaskWithURL(NSURL(string: myURL)!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
print("end")
}
loadDataTask!.resume()
}
It does work sensitively for the timeout. I suppose there may be something wrong with the delegator which cannot receive the response properly. Hope this helps.
I am trying to create HTTP request with Swift2 and to return response outside of nested function. My code looks like this:
let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? NSHTTPURLResponse
print(httpResponse)
print(data)
// return data
}
I would like to return data variable outside of nested function. Or to have some other variable defined before nested function, which I can set inside of nested function. Like this:
var test = "";
// some nested function
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
test = "test"
})
Anybody has some suggestion for this problem?
If you want to synchronously wait until the data task has finished so that you can return the fetched data, you have to use a semaphore.
func getDataSynchronously(request: NSURLRequest) -> NSData? {
var returnData: NSData?
let semaphore = dispatch_semaphore_create(0)
let dataTask = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) in
returnData = data
dispatch_semaphore_signal(semaphore)
})
dataTask.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return returnData
}
The semaphore will force the calling thread to stop and wait until it is signaled upon completion of the data task. Calling the above function would look like this
let request = NSURLRequest(URL: NSURL(string: "https://www.google.com")!)
let data = getDataSynchronously(request)
print("Synchronously fetched \(data!.length) bytes")
On the other hand, if you want to kick-off the data task in the background and be asynchronously notified about its completion, you can add your own completion block to the function's signature.
func getDataAsynchronously(request: NSURLRequest, completion: NSData? -> ()) {
let dataTask = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) in
completion(data)
})
dataTask.resume()
}
Calling the asynchronous method could look like this
let request = NSURLRequest(URL: NSURL(string: "https://www.google.com")!)
getDataAsynchronously(request) { data in
print("Asynchronously fetched \(data!.length) bytes")
}