5

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.

David Mordigal
  • 813
  • 2
  • 9
  • 22

1 Answers1

4

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

mugx
  • 9,869
  • 3
  • 43
  • 55
  • That worked! The only change I needed to make to the function signature was: `func attemptPost(_ completion:@escaping (Bool)->())`. Otherwise, that's pretty cool. So `completion` is a variable that gets written to when the task completes? – David Mordigal Nov 27 '17 at 03:23
  • Hi David, 'completion' is a parameter representing a code block, you may check here about Closures: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html – mugx Nov 27 '17 at 03:26