How can I stop URLSessionTask when the Internet is disconnected? - ios

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

Related

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

Function completes before NSURLSession - dataTaskWithRequest completed [duplicate]

This question already has an answer here:
NSURLConnection sendAsynchronousRequest can't get variable out of closure
(1 answer)
Closed 7 years ago.
I have the following method called under ViewDidLoad(). I understand that session.dataTaskWithRequest automatically runs in background thread. And because of the same, the code following this method in ViewDidLoad() does not wait for this process to complete and starts executing.
Is there any way that I can ensure that the background thread is completed before other methods are executed?
func getCoordinatesFromServer() {
let request = NSMutableURLRequest(URL: NSURL(string: constants.urlName.loadData)!)
request.HTTPMethod = "POST"
request.addValue("multipart/form-data", forHTTPHeaderField: "Accept")
request.setValue("keep-Alive", forHTTPHeaderField: "Connection")
request.HTTPBody = (constants.requestTag.getCoordinates).data
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response ,error ) in
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if (httpResponse.statusCode == 200) {
dispatch_async(dispatch_get_main_queue(), {
let decodedData = NSData(base64EncodedData: data!, options: NSDataBase64DecodingOptions([]))
let jsonText = NSString(data: decodedData!, encoding: NSASCIIStringEncoding) as! String
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonText.data, options: NSJSONReadingOptions.init(rawValue: 0))
self.parseJsonData(json["Datalist"] as! NSArray)
} catch {
print("Error:\n \(error)")
}
})
}
}
})
task.resume()
}
Regards,
if I understand your question, you can solve this problem at this way
For example:
class func ConnectedToNetwork(completionHandler: ((Status: Bool) -> Void))
{
let url = NSURL(string: "http://google.com")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "HEAD"
request.timeoutInterval = 0.2
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data,response, error) in
let httpResponse = response as! NSHTTPURLResponse
if httpResponse.statusCode == 200
{ completionHandler(Status: true)
return
}
}
task.resume()
}
and then, you can work with this
Checks.ConnectedToNetwork({ Status in dispatch_async(dispatch_get_main_queue())
{
if Status == true
{
//do what you want
}
});
dataTaskWithRequest is assync call, and it has completionHandler block. So all code that you wrote inside will be executed after the data task finished:
let request = NSURLRequest(URL: NSURL(string: "http://google.com")!)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {(data, response ,error ) in
print("3") // executed after data task is finished
print("4") // executed after data task is finished
})
task.resume()
print("1")
print("2")
import Foundation
// some aync function with completition handler, i MUST USE AS IT IS
func f(completitionHandler: ()->()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { () -> Void in
print("running concurrently")
sleep(1)
print("finished")
completitionHandler()
}
}
// some function running on main queue
func foo() {
// create dispatch group
let group = dispatch_group_create()
let myCompletitionHandler: ()->() = {
dispatch_group_leave(group)
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
dispatch_group_async(group, queue) { () -> Void in
dispatch_group_enter(group)
f(myCompletitionHandler)
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
}
foo()
print("foo finished")
It is not the best at all, the better solution is to run synchronous version of f() instead of this 'workaround':-)

How to set parameter from global function inside nested function in Swift 2

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

NSURLConnection sendAsynchronousRequest can't get variable out of closure

I'm trying to get a simple text response from a PHP page using POST. I have the following code:
func post(url: String, info: String) -> String {
var URL: NSURL = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
var output = "Nothing Returned";
request.HTTPMethod = "POST";
var bodyData = info;
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
output = (NSString(data: data, encoding: NSUTF8StringEncoding))!
}
return output
}
While this code does not throw any errors, when I make a call to it like this:
println(post(url, info: data))
It only prints: "Nothing Returned" even though if I were to change the line:
output = (NSString(data: data, encoding: NSUTF8StringEncoding))!
to this:
println((NSString(data: data, encoding: NSUTF8StringEncoding)))
it does print out the proper response. Am I doing something wrong with my variables here?
This is calling asynchronous function that is using a completion handler block/closure. So, you need to employ the completion handler pattern in your own code. This consists of changing the method return type to Void and adding a new completionHandler closure that will be called when the asynchronous call is done:
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { response, data, error in
guard data != nil else {
completionHandler(nil, error)
return
}
completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
}
}
Or, since NSURLConnection is now formally deprecated, it might be better to use NSURLSession:
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
completionHandler(nil, error)
return
}
completionHandler(NSString(data: data!, encoding: NSUTF8StringEncoding), nil)
}
}
task.resume()
return task
}
And you call it like so:
post(url, info: info) { responseString, error in
guard responseString != nil else {
print(error)
return
}
// use responseString here
}
// but don't try to use response string here ... the above closure will be called
// asynchronously (i.e. later)
Note, to keep this simple, I've employed the trailing closure syntax (see Trailing Closure section of The Swift Programming Language: Closures), but hopefully it illustrates the idea: You cannot immediately return the result of an asynchronous method, so provide a completion handler closure that will be called when the asynchronous method is done.

Resources