Reputation: 270790
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:
ResultsViewController
is shownAfter some trying, I can only get this:
ResultsViewController
is shown, with no resultsResultsViewController
don't know.P.S. I don't know how to improve the question title...
Upvotes: 0
Views: 78
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