Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sync serial queue for URLSession tasks?

Using XCode-8.2.1, Swift-3.0.2 and iOS-10.2.1,

I am trying to call two different URLSession.shared.dataTasks (the first is a simple URL-request and the second is a POST-request).

Since my first dataTask delivers a result that is needed in the httpBody of the second dataTask, the two URLSession.shared.dataTasks shall run in series, one after the other! (and also the preparative code shall run consecutively).

I tried, so far, using two consecutive serialQueue.sync{} queues. But I had to realize that the code does not perform in the order I would like to.

The print-statement in the log turn out to be as follows:

Hmmmmmm 2
Hmmmmmm 1
Hmmmmmm 3

(instead of 1, 2, 3 as needed)!

How can you get the order 1, 2, 3 ??

(i.e. how can you make sure the httpBody of the second dataTask can be filled with a result coming from the first dataTask ?)

Here is my code: (not executable as URL's were taken out - but you get the point)!

import UIKit

class ViewController: UIViewController {

    let serialQueue = DispatchQueue(label: "myResourceQueue")
    var stationID: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


        self.serialQueue.sync {
            let myResourceURL = URL(string: "myQueryString1")
            let task = URLSession.shared.dataTask(with: myResourceURL!) { (data, response, error) in
                if (error != nil) {
                    // print(error.debugDescription)
                } else {
                    if let myData = data {
                        do {
                            let myJson = try JSONSerialization.jsonObject(with: myData, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
                            // print(myJson)
                            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                            print("Hmmmmmm 1")
                            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                        } catch {
                            // error
                        }
                    }
                }
            }
            task.resume()
        }
        self.serialQueue.sync {
            var request = URLRequest(url: URL(string: "myQueryString2")!)
            request.httpMethod = "POST"
            request.addValue("API_id", forHTTPHeaderField: "Authorization")
            request.addValue("application/xml", forHTTPHeaderField: "Content-Type")
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            print("Hmmmmmm 2")
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            let postString: String = "My_XML_POST_Body"
            request.httpBody = postString.data(using: .utf8)
            let task = URLSession.shared.dataTask(with: request) { data, response, error in
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                print("Hmmmmmm 3")
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            }
            task.resume()
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Any help appreciated!

like image 245
iKK Avatar asked Feb 05 '17 19:02

iKK


People also ask

What will happend if you don't invalidate the session URLSession explicitly?

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don't invalidate the session, your app leaks memory until the app terminates.

What is API or have you used URLSession Class What does it do?

The URLSession API offers an easy-to-use, modern API for networking. It offers flexibility and several features many developers have been asking for. If a project can do without another dependency, then that's something worth considering. URLSession is the successor of NSURLConnection .

How do I use URLSession in Swift 5?

let url = URL(string: “https://example.com/post”)! With the above code, we're first creating a session constant with the shared URLSession instance, and we set up a URL object that refers to https://example.com/post. Then, with that url object we create an instance of URLRequest and assign it to the request variable.

What is URLSession shared?

For basic requests, the URLSession class provides a shared singleton session object that gives you a reasonable default behavior for creating tasks. Use the shared session to fetch the contents of a URL to memory with just a few lines of code.


1 Answers

I finally found a solution:

Inspired by this answer, I introduced a URLSessionDataDelegate, together with its delegate callback-methods (i.e. didReceive response:, didReceive data: and didCompleteWithError error:.

Important: You need to set up your URLSession with a delegate in order to make the introduced URLSessionDelegate's callback methods work: Use URLSession(configuration: ....) for this like shown here:

let URLSessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: URLSessionConfig, delegate: self, delegateQueue: OperationQueue.main)

After that, you are good to go, i.e. the log is as expected now:

Hmmmmmm 1
Hmmmmmm 2
Hmmmmmm 3

Here is the final code (again not executable as URL's were taken out - but you get the point)!

import UIKit

class ViewController: UIViewController, URLSessionDataDelegate {

    var stationID: Int = 0
    let URLSessionConfig = URLSessionConfiguration.default
    var session: URLSession?
    var task1: URLSessionTask?
    var task2: URLSessionTask?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.session = URLSession(configuration: URLSessionConfig, delegate: self, delegateQueue: OperationQueue.main)

        // prepare dataTask Nr 1
        let myResourceURL = URL(string: "myQueryString1")
        self.task1 = session?.dataTask(with: myResourceURL!)

        // start dataTask Nr 1 (URL-request)
        self.task1?.resume()
    }

    // Optional: Use this method if you want to get a response-size information 
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {

        // print(Int(response.expectedContentLength))
        completionHandler(URLSession.ResponseDisposition.allow)
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {

        if dataTask == self.task1 {
           do {
              let myJson = try JSONSerialization.jsonObject(with: myData, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
              // print(myJson)
              // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              print("Hmmmmmm 1")
              // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

              // prepare dataTask Nr 2
              self.task2 = self.session?.dataTask(with: self.prepareMyURLRequest())
           } catch {
              // error
           }
        } else if dataTask == self.task2 {

            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            print("Hmmmmmm 3")
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        } else {
            print("unknown dataTask callback")
        }
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
       if (error != nil) {
          // print(error.debugDescription)
       } else if task == self.task1 {

          // start dataTask Nr 2 (POST URL-request)
          self.task2?.resume()
       }
    }

    func prepareMyURLRequest() -> URLRequest {
        var request = URLRequest(url: URL(string: "myQueryString2")!)
        request.httpMethod = "POST"
        request.addValue("API_id", forHTTPHeaderField: "Authorization")
        request.addValue("application/xml", forHTTPHeaderField: "Content-Type")
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        print("Hmmmmmm 2")
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        let postString: String = "My_XML_POST_Body"
        request.httpBody = postString.data(using: .utf8)
        return request
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}
like image 68
iKK Avatar answered Oct 03 '22 06:10

iKK