swift asynchronous request using dispatch_group_notify doesn't work - ios

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

Related

iOS - Swift : fetching data from database in main thread, not in background

In my iOS App i'm able to download data from a database, but actually all the operations are made in background and the main thread is still active, even the GUI. I also tried to make a 'sleep' with
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { ... }
With this delay everthing works fine, but it's not a good solution. How can i change my code to do this in the main thread? Possibly with loadingIndicator.
This is my code (checking if username exists):
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String)
{
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
var _errorMsg = ""
var _psw = ""
var parameters : [String : Any]?
parameters = ["username": username,
"action": "login"]
print(parameters!)
let session = URLSession.shared
let url = "http://www.thetestiosapp.com/LoginFunctions.php"
let request = NSMutableURLRequest()
request.url = URL(string: url)!
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField:"Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters!, options: .sortedKeys)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
_psw = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
passwordFromDb = _psw
errorMsg = _errorMsg
}
You’re attempting to update passwordFromDb and errorMsg at the end of this method. But this is an asynchronous method and and those local variables _psw and _errorMsg are set inside the closure. Rather than trying to defer the checking of those variables some arbitrary three seconds in the future, move whatever “post request” processing you need inside that closure. E.g.
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let parameters = ...
let session = URLSession.shared
var request = URLRequest()
...
do {
request.httpBody = ...
let task = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse,
let statusCode = httpResponse.statusCode {
print ("status code = \(statusCode)")
}
guard let data = data else {
print (error ?? "Unknown error")
return
}
let password = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
DispatchQueue.main.async {
// USE YOUR PASSWORD AND ERROR MESSAGE HERE, E.G.:
self.passwordFromDb = password
self.errorMsg = _errorMsg
// INITIATE WHATEVER UI UPDATE YOU WANT HERE
}
}
task.resume()
} catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
}

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

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':-)

Notify when a background request has finished swift

I'm sending a request to my backend server and I need to know the right way to notify my main thread the response. I'm using the NSNotificationCenter for this task but this not work sometimes and has some delay when it works because when I'm debugging I can see when the console prints the result but then a few secs later the corresponding alert to appear.
Here is my request:
let request1 = NSMutableURLRequest(URL: NSURL(string: serverID)!)
request1.HTTPMethod = "GET"
for key in parameters.keys{
request1.addValue(parameters[key] as String!, forHTTPHeaderField: key)
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request1) {
data, response1, error in
if error != nil {
print("error=\(error)")
NSNotificationCenter.defaultCenter().postNotificationName("request1Error", object: self)
return
}
else {
let httpResponse = response1 as! NSHTTPURLResponse
let headers = httpResponse.allHeaderFields as NSDictionary
let sucess: AnyObject? = headers.objectForKey("UserId")
if (sucess != nil){
let value = headers.valueForKey("info") as! String
print("info: \(value)")
NSNotificationCenter.defaultCenter().postNotificationName("sucessRequest", object: self)
}
else{
NSNotificationCenter.defaultCenter().postNotificationName("requestError2", object: self)
}
}
}
task.resume()
How should I notify my main thread the result of the request?
I don't know what exactly do you mean with main thread and if that is just a main thread (and not another controller or something similar) I think you should use closures that will get executed on main thread by using dispatch async.A function that described what I wrote would look similar like this :
func request(onSuccess : (value:String) -> Void, onError: ()->Void){
let request1 = NSMutableURLRequest(URL: NSURL(string: serverID)!)
request1.HTTPMethod = "GET"
for key in parameters.keys{
request1.addValue(parameters[key] as String!, forHTTPHeaderField: key)
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request1) {
data, response1, error in
if error != nil {
print("error=\(error)")
NSNotificationCenter.defaultCenter().postNotificationName("request1Error", object: self)
return
}
else {
let httpResponse = response1 as! NSHTTPURLResponse
let headers = httpResponse.allHeaderFields as NSDictionary
let sucess: AnyObject? = headers.objectForKey("UserId")
if (sucess != nil){
let value = headers.valueForKey("info") as! String
print("info: \(value)")
dispatch_async(dispatch_get_main_queue(),{
onSuccess(value)
})
}
else{
dispatch_async(dispatch_get_main_queue(),{
onError()
})
}
}
}
task.resume()
}
The delay is due to the fact that you're trying use the NSNotificationCenter on the same thread that you're using NSURLSession on. Try updating your calls to the NSNotificationCenter with something along the lines of:
dispatch_async(dispatch_get_main_queue()) {
NSNotificationCenter.defaultCenter().postNotificationName("sucessRequest", object: self)
}

Resources