Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell the main thread that a URLSessionDataTask has finished

Using Swift 4, I have this code that attempts a POST request to a REST API:

spinner.startAnimation(self)
btnOk.isEnabled = false
btnCancel.isEnabled = false

attemptPost()

spinner.stopAnimation(self)
btnOk.isEnabled = true
btnCancel.isEnabled = true

The function that does this (Constants and Request are classes that I created that create the request objects and hold frequently used data):

func attemptPost() {
    let url = Constants.SERVICE_URL + "account/post"
    let body: [String : Any] =
        ["firstName": txtFirstName.stringValue,
        "lastName": txtLastName.stringValue,
        "email": txtEmail.stringValue,
        "password": txtPassword.stringValue];
    let req = Request.create(urlExtension: url, httpVerb: Constants.HTTP_POST, jsonBody: body)
    let task = URLSession.shared.dataTask(with: req) { data, response, err in
        guard let data = data, err == nil else {
            // error
            return
        }

        if let resp = try? JSONSerialization.jsonObject(with: data) {
            // success
        }
    }

    task.resume()
}

Since the task that does this runs asynchronously, there is no sequential way that I can update the UI once the call to attemptPost() returns. And since the UI components are on the main thread, I can't directly update the components from the task that makes the request.

In C# it works the same way; there is a BackgroundWorker class in which you can safely update the UI components to avoid a "Cross-thread operation not valid" error.

I'm trying to find an example that accomplishes more or less the same thing, in which a "wait" state is established, the task runs, and upon task completion, the main thread is notified that the task is done so that the wait state can be changed.

But I'm still having trouble understanding how this all comes together in Swift. I've looked around and seen information about the handlers that are invoked from within URLSessionDataTask and stuff about GCD, but I'm still not able to connect the dots.

And is GCD even relevant here since the URLSessionDataTask task is asynchronous to begin with?

Any help is appreciated.

like image 257
David Mordigal Avatar asked Nov 27 '17 03:11

David Mordigal


1 Answers

If I understood correctly you might try this solution:

spinner.startAnimation(self)
btnOk.isEnabled = false
btnCancel.isEnabled = false

attemptPost { (success) in
    DispatchQueue.main.async {
        spinner.stopAnimation(self)
        btnOk.isEnabled = true
        btnCancel.isEnabled = true
    }

   // UI wise, eventually you can do something with 'success'
}

func attemptPost(_ completion:@escaping (Bool)->())
    let url = Constants.SERVICE_URL + "account/post"
    let body: [String : Any] =
        ["firstName": txtFirstName.stringValue,
         "lastName": txtLastName.stringValue,
         "email": txtEmail.stringValue,
         "password": txtPassword.stringValue];
    let req = Request.create(urlExtension: url, httpVerb: Constants.HTTP_POST, jsonBody: body)
    let task = URLSession.shared.dataTask(with: req) { data, response, err in
        guard let data = data, err == nil else {
            completion(false)
            return
        }

        if let resp = try? JSONSerialization.jsonObject(with: data) {
            completion(true)
        }
    }
    task.resume()
}

so the idea is executing from attemptPost a block which will run asynchronously into the main thread your UI stuff

like image 180
Andrea Mugnaini Avatar answered Nov 05 '22 04:11

Andrea Mugnaini