Why doesn't NSURLSession.dataTaskWithURL() call my completion handler? - ios

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()
}

Related

How can I stop URLSessionTask when the Internet is disconnected?

I am using URLSessionTask to get the source code of url. When the internet is connected, it works well.
However, when the Internet is disconnected, I try building. And in simulator it is blank and the cpu is 0%. What affects is that My Tab Bar Controller is also missing and blank (It is my initial view controller). It seems that this task is under connecting?
I want the data received from dataTask, so I use semaphore to make it synchronous. Otherwise, as dataTask is an asynchronous action, what I
get is an empty string.
How can I fix this problem?
Thanks!
let urlString:String="http://www.career.fudan.edu.cn/jsp/career_talk_list.jsp?count=50&list=true"
let url = URL(string:urlString)
let request = URLRequest(url: url!)
let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)
let dataTask = session.dataTask(with: request,
completionHandler: {(data, response, error) -> Void in
if error != nil{
errorString = "Error!"
}else{
htmlStr = String(data: data!, encoding: String.Encoding.utf8)!
//print(htmlStr)
}
semaphore.signal()
}) as URLSessionTask
//start task
dataTask.resume()
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
Update: As #Moritz mentioned, I finally use completion handler (callback).
func getforData(completion: #escaping (String) -> ()) {
if let url = URL(string: "http://XXXXX") {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data, let getString = String(data: data, encoding: String.Encoding.utf8), error == nil {
completion(getString)
} else {
print("error=\(error!.localizedDescription)")
}
}
task.resume()
}
}
And in viewdidload
override func viewDidLoad() {
super.viewDidLoad()
getforData { getString in
// and here we get the "returned" value from the asynchronous task
print(getString) //works well
//tableview should work in main thread
DispatchQueue.main.async {
self.newsTableView.dataSource = self
self.newsTableView.delegate = self
self.newsTableView.reloadData()
}
}

Synchronous API request to Asynchronous API request Swift 2.2

Well I am new to Swift and I don't know much of completion handler. I want to get a request from an API and parse the JSON response so I can get the token. But what's happening with my code is that whenever I call the getAuthentication function my UI freezes and waiting for the data to get. Here is the code for getAuthentication
func getAuthentication(username: String, password: String){
let semaphore = dispatch_semaphore_create(0);
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
dispatch_semaphore_signal(semaphore);
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
then, I am calling this method in my LoginViewController. Someone says that I am using a Synchronous request thats why my UI freezes, but I have really no idea on how to change it to Async and wait for the data to be downloaded. Can someone help me with this? Any help will much be appreciated.
Firstly, remove dispatch_semaphore related code from your function.
func getAuthentication(username: String, password: String){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
}
task.resume()
}
In the above code, the function dataTaskWithRequest itself is an asynchronus function. So, you don't need to call the function getAuthentication in a background thread.
For adding the completion handler,
func getAuthentication(username: String, password: String, completion:((sucess: Bool) -> Void)){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
var successVal: Bool = true
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
successVal = false
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(successVal)
})
}
task.resume()
}
It can be called as follows:
self.getAuthentication("user", password: "password", completion: {(success) -> Void in
})
You may pass an escaping closure argument to getAuthentication method.
func getAuthentication(username: String, password: String, completion: (JSON) -> ()){
...
// create a request in the same way
...
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
completion(swiftyJSON)
} else {
print("There was an error")
}
}
task.resume()
}
And call getAuthentication in LoginViewController like this:
getAuthentication(username, password) { (json) -> in
//Do whatever you want with the json result
dispatch_async(dispatch_get_main_queue()) {
// Do UI updates
}
}
Another way to go is calling getAuthentication in a background thread in your LoginViewController to avoid blocking the main thread (i.e. UI thread).
//In LoginViewController
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
getAuthentication(username, password)
dispatch_async(dispatch_get_main_queue()) {
// UI updates
}
}

swift asynchronous request using dispatch_group_notify doesn't work

I'm trying to use dispatch_group_notify to send a HTTP request where I need to wait for the result of this command before continuing my processing.
here is the following call:
self.save(){(response) in
if let result = response as? Bool {
if(result == true){
dispatch_group_notify(self.myGroup!, dispatch_get_main_queue(), {
print("send carnet finished")
let registrationView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("carnetTable") as! CarnetTableViewController
self.navigationController?.pushViewController(registrationView, animated: true)
})
}
}
}
and here is the function who is sending the HTTP command:
func save(callback: (AnyObject) -> ()){
dispatch_group_enter(self.myGroup)
let p = pickerDataSource[patients.selectedRowInComponent(0)]
let params = "owner=\(User.sharedInstance.email)&patient=\(p)&carnet=\(commentaires.text!)"
let final_url = url_to_request + "?" + params.stringByAddingPercentEncodingForISOLatin1()!
print("URL addCarnet: \(final_url)")
let url:NSURL = NSURL(string: final_url)!
//let session = NSURLSession.sharedSession()
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration,
delegate: self,
delegateQueue:NSOperationQueue.mainQueue())
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
request.timeoutInterval = 10
let task = session.dataTaskWithRequest(request) {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error data")
dispatch_group_leave(self.myGroup)
callback(false)
return
}
var result = NSString(data: data!, encoding:NSASCIIStringEncoding)!
print("result: \(result)")
}
task.resume()
dispatch_group_leave(self.myGroup)
callback(true)
}
I would like to ensure that save function is finished (dispatch_group_leave) before opening the new ViewController (CarnetTableViewController) but I can see that ViewController is called before the end of the dispatch_group...
how can I ensure the end of the save function before opening the new View ?
The last three lines of your function:
task.resume()
dispatch_group_leave(self.myGroup)
callback(true)
This causes the task to start, and then you immediately (before the task has completed), leave the group and call the callback.
If you trace through the code, your dispatch_group_enter and dispatch_group_leave occur in the same scope, on the same queue, and before you call callback(). That means they're not actually doing anything. By the time you get to your callback, the dispatch_group is empty.
If you had an error, I'd expect a problem when that error-leg calls dispatch_group_leave a second time (since this is unbalanced).
You meant this:
...
var result = NSString(data: data!, encoding:NSASCIIStringEncoding)!
print("result: \(result)")
dispatch_group_leave(self.myGroup)
callback(true)
}
task.resume()

Handle losing connection

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.

Rest API by Swift in IOS is not executed

I am using REST API to get data from server using swift 2. When I call the function below, something happens, and the func getAllServiceName(data :NSData) is not executed. I tried many things but I did not have success.
func getServiceName () {
let urlServiceName = NSURL(string: "urllinkOfMe")
let request = NSURLRequest(URL: urlServiceName!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
self.getAllServiceName(data!)
}
}
func getAllServiceName(data :NSData)
{
let dictServicename: NSDictionary!=(try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
if(dictServicename["data"] != nil){
let dataTable :NSArray = dictServicename.valueForKey("data") as! NSArray
for var i = 0 ; i < dataTable.count ; i++
{
//pickOptionVas.addObject(dataTable[i].valueForKey("serviceName")!)
}
}
}
Here is what you used to get data from server by calling API:
func getServiceName () {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let urlServiceName = NSURL(string: "http://188.166.232.101:1000/hs/vasService/getServiceName")
let request = NSMutableURLRequest(URL: urlServiceName!)
request.HTTPMethod = "GET"
let postTask = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
print(data)
})
//This is necessary
postTask.resume()
}
Go for the above code and let me know you are getting the data or not!
What's the response code? if you get 500 perhaps something wrong with your request parameter or your REST API is not implemented correctly.

Resources