Sweeper
Sweeper

Reputation: 270790

Where should I put this code that should be done in background?

I am building an app that calculates stuff.

The user will enter his/her inputs in OperationViewController and then he/she will click "Calculate". After calculating the results, a segue is performed to show the ResultsViewController.

Some of the calculations will take a long time, so I think they should be done in the background thread. And I should show a message saying that it is calculating and an activity indicator.

I also grabbed some code from somewhere that makes doing things in the background super swifty. Here is the code:

import Foundation
infix operator ~> {}

/**
 Executes the lefthand closure on a background thread and,
 upon completion, the righthand closure on the main thread.
 Passes the background closure's output, if any, to the main closure.
 */
func ~> <R> (
    backgroundClosure: () -> R,
    mainClosure:       (result: R) -> ())
{
    dispatch_async(queue) {
        let result = backgroundClosure()
        dispatch_async(dispatch_get_main_queue(), {
            mainClosure(result: result)
        })
    }
}

/** Serial dispatch queue used by the ~> operator. */
private let queue = dispatch_queue_create("serial-worker", DISPATCH_QUEUE_SERIAL)

Then, the problem arises.

In OperationViewController, there is a method called getResults:

private func getResults () -> [(name: String, from: String, result: String)]? {
    // irrelevent code about getting the user's inputs from UITextFields
    return operation.calculate(input) // This will take a few seconds
}

The calculate method there requires a few seconds to return.

The Calculate button, mentioned earlier has a segue that's connected to ResultsViewController. I didn't explicitly call performSegueWithIdentifier. I just control dragged the button to the results view controller.

In prepareForSegue, I call getResults:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "showOperationHelp" {
        // irrelevent
    } else if segue.identifier == "showResults" {
        let vc = segue.destinationViewController as! ResultsViewController
        vc.results = getResults()
    }
}

Now I tried to put the getResults part in a background thread by doing this:

} else if segue.identifier == "showResults" {
    let vc = segue.destinationViewController as! ResultsViewController;
    { self.getResults() } ~> { vc.results = $0 };
}

But that doesn't work because prepareForSegue returns before the calculation finish. This means that vc.results is nil when prepareForSegue returns. This will cause the ResultsViewController to show nothing.

Another method I have tried is to put the "do in background" thingy in getResults:

private func getResults () -> [(name: String, from: String, result: String)]? {
    // irrelevent code about getting the user's inputs from UITextFields
    var results: [(name: String, from: String, result: String)]?;
    { self.operation.calculate(input) } ~> { results = $0 };
    return results
}

Again, getResults will just return nil.

Back then when I was writing C#, I can use the async/await keywords to achieve this.

I can just do something like:

var results = await operations.Calculate(input);

When execution reaches await, it pauses there and allows the UI thread to go on. After the async operation has finished, the execution goes back to where it had stopped and continues on.

Question: Can I do something like the above in Swift? If I can't, how can I wait until the calculations finish and show ResultsViewController?

Just in case you didn't understand me, I'll describe exactly what I want:

  1. the user enters some inputs
  2. the user taps the calculate button
  3. A message shows up telling the user that it is calculating
  4. Some time later
  5. Calculation finishes
  6. ResultsViewController is shown

After some trying, I can only get this:

  1. the user enters some inputs
  2. the user taps the calculate button
  3. A message shows up telling the user that it is calculating
  4. ResultsViewController is shown, with no results
  5. Some time later
  6. Calculation finishes but ResultsViewController don't know.

P.S. I don't know how to improve the question title...

Upvotes: 0

Views: 78

Answers (1)

JAB
JAB

Reputation: 3235

Editing original response for clarity:

  • Have the the calculate button call a method called calculate() or something that shows the activity indicator, then calls the getResults method.

  • give getResults() a completionHandler, and on successful completion dismiss the activity indicator and perform the segue with the completed calculation.

Upvotes: 2

Related Questions