Nested Assync URL datatask

Hi,


I'm using Swift 2 and need to do 2 two HTTP Post requests. In the first, I need to write some command to an REST server and if the result be successfull, do another request for reading some data from it. In order to perform it I've developed the funtios below:


public func commandWrite(tag:String, value:Int16, completionHandler:(Bool, String)-> Void) {

var url = NSURL(string: (urlBase as String) + "write")!

var request = NSMutableURLRequest(URL: url)

request.HTTPMethod = "POST"

request.addValue("application/json", forHTTPHeaderField: "Content-Type")

var error : NSErrorPointer = NSErrorPointer()

var dictData: NSMutableDictionary = NSMutableDictionary()

dictData.setValue(tag, forKey: "id")

dictData.setValue(String(value), forKey: "v")

let postData = NSJSONSerialization.dataWithJSONObject([dictData], options: NSJSONWritingOptions(0), error: error)



request.HTTPBody = postData as NSData!

var response : NSURLResponse = NSURLResponse()

var session = NSURLSession.sharedSession()

var jsonData: NSDictionary = NSDictionary()

var task = session.dataTaskWithRequest(request){ data, response, error -> Void in

if(response) != nil{

if(data) != nil{

jsonData = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary

println(jsonData)

if(jsonData.count > 0){

let usefullData = jsonData.valueForKey("writeResults") as! NSArray

let success = usefullData[0].valueForKey("s") as! Bool

var errorMsg = ""

if success == false{

error = dados[0].valueForKey("r") as! String

}

completionHandler(success,errorMsg)

}

else{

completionHandler(false,"Without server connection.")

}

}

else{

completionHandler(false,"Without server connection.")

}

}

else{

completionHandler(false,"Without server connection.")

}

}

task.resume()

}


public func tagRead(tag:String, completionHandler:(Bool, String, Double)-> Void){

var url = NSURL(string: (urlBase as String) + "read")!

var request = NSMutableURLRequest(URL: url)

request.HTTPMethod = "POST"

request.addValue("application/json", forHTTPHeaderField: "Content-Type")

var error : NSErrorPointer = NSErrorPointer()

let postData = NSJSONSerialization.dataWithJSONObject([tag], options: NSJSONWritingOptions(0), error: error)

request.HTTPBody = postData as NSData!

var response : NSURLResponse = NSURLResponse()

var session = NSURLSession.sharedSession()

var jsonData: NSDictionary = NSDictionary()

var task = session.dataTaskWithRequest(request) { data, response, error -> Void in

if(response) != nil{

if(data) != nil{

jsonData = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary

println(jsonData)

if(jsonData.count > 0){

let usefullData = jsonData.valueForKey("readResults") as! NSArray

let success = usefullData[0].valueForKey("s") as! Bool

var value = 0.0

var errorMsg = ""

if success{

value = data[0].valueForKey("v") as! Double

} else{

errorMsg = data[0].valueForKey("r") as! String

}

completionHandler(success,errorMsg, value)

}

completionHandler(false,"Without server connection.", 0.0)

}

}

else{

completionHandler(false,"Without server connection.", 0.0)

}

}

else{

completionHandler(false,"Without server connection.", 0.0)

}

}

task.resume()

}


I've read that for use such assync calls its necessary to use between "{" and "}", so I tried to do something like:


func getData(){

let tag = tagBase + (selEqp as String) + "." + textFieldBlocoMedidor.text

var sucEsc:Bool = false

var erroLiga:String = ""

var estado:Double = 0

writeCommand((tag + "_CMD1"), valor: 4){ sucWrite, errorWrite in

ProgressVale.shared.showOverlay(self.view)

if (sucWrite){

var sucLeit: Bool

var erroLeit:String

readTag(tag + "_EST1"){ sucRead, errorRead, value in

ProgressVale.shared.hideOverlayView()

if (sucRead){

self.textFieldData.text = String(format: "%.1f", value as Double)

let alert = UIAlertView()

alert.title = "Warning!"

alert.message = "Data read is: \(value)"

alert.addButtonWithTitle("OK")

alert.show()

}else{

let alert = UIAlertView()

alert.title = "Warning!"

alert.message = errorRead

alert.addButtonWithTitle("OK")

alert.show()

}

}

}else{

ProgressVale.shared.hideOverlayView()

let alert = UIAlertView()

alert.title = "Warning!"

alert.message = errorWrite

alert.addButtonWithTitle("OK")

alert.show()

}


}

}


The getData funcion works fine, I can see on println's debugger that the writeCommand and readTag sequence happens correctly and fast as expected. But it seems there is some delay to show alert views, in case of successfull or error and, in case of sucess, self.textFiledData.text is completed only few seconds (10~30) after readTag's completion handler have been executed. Could anyone help me?

Regards

Replies

But it seems there is some delay to show alert views …

The most common reason for this is that you’re presenting the alert from a secondary thread, which is not allowed. Most of UIKit is only safe to call from the main thread.

When you use the NSURLSession convenience APIs (those with a completion handler closure) on the shared session, the completion handler is called on some arbitrary background thread. If you want to access the UI, you’ll have to bounce to the main thread.

You can test this theory by calling code like this just before the code that puts up the alert:

assert(NSThread.isMainThread())

If the assert fires, you know you have problems.

ps You’re aware that Xcode 8.2 is the last version of Xcode that supports Swift 2, right? It’s time to move forward to Swift 3.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn,


I did exactly what yo've suggested and the error was assertion failed: : file /Users/valedifs/Desktop/Vcom -20170129/Vcom/AbastecimentoTanqueViewController.swift, line 307.

How should I change my code? Just wanna say that I need to use request response data to do some calculations, show alert views, fill textfields and I want to use the function above in several view controllers, that is I appreciate if it were possible to use this functions and get the return and not using global variables. Could you help me?


Thanks.

I did exactly what yo've suggested and the error was assertion failed

Cool. Well, not cool, but at least you now know what the problem is.

How should I change my code?

The most direct solution is to bounce to the main thread via GCD. In Swift 2 this looks like this:

dispatch_async(dispatch_get_main_queue()) {
    … this code runs on the main queue, which implies the main thread …       
}

However, my experience is that dispatching around to different queues just opens you up to a bunch of race conditions, and it’s better to take a more measured approach to concurrency. I explained my recommended structure in this post.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi,


I tried to do what you suggested but it doesn't work. So I try to insert dispatch_async(dispatch_get_main_queue()) and doesn't work too. Is there a way for make post sync post? In my case the problem is that I need to do 2 posts, the second one I will do onlyt if the first was successfull. I saw that the second completion handler was not on main thread, even I put the dispatch code above.

So I try to insert

dispatch_async(dispatch_get_main_queue())
and doesn't work too.

Define “doesn’t work” in this context. Are you saying that it doesn’t get you to the main thread? Or that it doesn’t help you serialise your posts.

Is there a way for make post sync post?

That is not recommend and, anyway, shouldn’t be necessary.

In my case the problem is that I need to do 2 posts, the second one I will do onlyt if the first was successfull.

A simple structure for this would be:

session.dataTask(… first POST request …) {
    if let error = error {
        … dispatch to main thread and post the error …
    } else {
        … process server result …
        if unsuccessful {
            … dispatch to main thread and post the error …
        } else {
            session.dataTask(… second POST request …) {
                if let error = error {
                    … dispatch to main thread and post the error …
                } else {
                    … process server result …
                    if unsuccessful {
                        … dispatch to main thread and post the error …
                    } else {
                        … dispatch to main thread and post success …
                    }
            }
        }.resume()
    }
}.resume()

There are lots of better ways to structure this code but I recommend you start with the above because it’s really straightforward.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn,

It works like the way you've suggested. Unfortunately, I don't have time for a better approach, because my project is a short-time one and I'm adapting some existing code to new functionalities. In this way, I have another question: I have two assync functions that are sequential in some part of code, I need to show some pop-up alert for user, as soon as the last of them has completed. Could you please help me on this? Thanks a lot

I have two assync functions that are sequential in some part of code, I need to show some pop-up alert for user, as soon as the last of them has completed.

You can do this by extending the ideas I’ve shown above. The only gotcha is that you have to bounce to the main thread/queue before touching UIKit. So, it’d look something like this.

In the code below I’m using an

func executeSomeFakeNetworking() {
    NSLog("start 1")
    DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
        NSLog("after 1, start 2")
        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
            NSLog("after 2")
            DispatchQueue.main.async {
                NSLog("after 2 on main queue, show UI")
                self.showCompletionUI()
            }
        }
    }
}

func showCompletionUI() {
    let av = UIAlertView(title: "Test", message: nil, delegate: nil, cancelButtonTitle: "OK")
    av.show()
}

Note that in this test code I’m using

asyncAfter(…)
as a placeholder for an async network operation. Also note the log I get from running this:
2017-02-03 09:20:08.282 xxsi[6971:332675] start 1
2017-02-03 09:20:09.369 xxsi[6971:332717] after 1, start 2
2017-02-03 09:20:11.369 xxsi[6971:332717] after 2
2017-02-03 09:20:11.370 xxsi[6971:332675] after 2 on main queue, show UI

and specifically the times in the log.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn,


But in fact my code is something like this:

func executeTasks(){

executeTask1()

executeTask2()

}


and in executeTask2 I have something like

func executeTask2(){

...

showPopUp()

...

}


executeTask1 e executeTask2 contains several webservers access, with NSURL. I need to show popup only after executeTask1 and executeTask2 have completed, and my popu depends on executeTask1 and executeTask2 results. For example, if executeTask2 have been completed I need to show it, but only after executeTask2 has already been completed, because I have a ProgressBar on screen.

You can manage dependencies like this by hand. For example:

  • You could have a counter of outstanding tasks, having each task decrement the counter when it completes, and have the one that decrements it to 0 show the UI

  • You could have flags for each type of task, with each task clearing its flag when it’s done and, if all the flags are clear, show the UI

IMPORTANT Both of the above involve maintaining shared state, states whose access must be serialised. You can use the main queue for that, or with something like a serial OperationQueue or DispatchQueue.

Personally, in situations like this, where I have to dealing with complex dependencies between async tasks, I generally reach for

Operation
(aka NSOperation) and its explicit dependency management. Or, in the really complex cases, I explicitly model the state as a state machine.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn,

But I saw some another complication have occured. As told, I want to do showPopUp on conditions below:

func executeTasks(){

executeTask1()

executeTask2()

}

and in executeTask2 I have something like

func executeTask2(){

...

showPopUp()

...

}

I'm using a serial queue, but on the existing code I'm working on, there are a severial of web async request inside executeTaks1, that is:

func executeTask1(){

do web_request_1

if web_request_1 return sucess{

executeTask1_1()

}

}

func executeTask1_1(){

do web_request_2

if web_request_2 return sucess{

executeTask1_2()

}

}

...

func executeTask1_n(){

do web_request_n

if web_request_n return sucess{

true

}

}

I want to executeTask2 happens only affter Task1_n has completed, on this contidion. The problem is that executeTask1 is performed in another parts of code, without executeTask2. I'm sorry for the message, but I'm dealing with some existing code and, as you could see, its design is not good at all.

Thanks.

I don’t think there’s a ‘magic bullet’ answer to problems like this. You can either:

  • Refactor your networking to use some coherent approach (NSOperations, reactive signals, or whatever) and then use that technology to model these relationships at a high level.

  • Continue with your ad hoc approach, using completion handlers, delegates, notifications and so on to wrangle these dependencies.

The first approach is definitely preferred, offering long-term benefits for short-term pain.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"