LulzCow
LulzCow

Reputation: 1229

Wait for return in a func that dispatches an asyncronous operation (Swift + Parse)

This is a fundamental problem with executing asynchronous blocks, and I haven't found a good solution yet.

The following code works, but it blocks the main thread, and I'd like to avoid that.

override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
    //check if transaction already exists
    let trans = PFQuery(className: "Transactions")
    trans.whereKey("itemId", equalTo: self.pfObject.objectId!)

   //I need the count of objects in the query before I can proceed, but I don't want to block the main thread
   let count = trans.countObjects()
   if(count > 0){
        return false
    }else{
        return true
    }
}

This problem isn't specific to this part of my application. Normally, I can just set "count" (or whatever variable I need) in the closure of something like query.countObjectsInBackGroundWithBlock(), but I can't do that when I need to return something on the main thread.

Is there a solution to make my application wait for return without blocking the main thread? I actually don't think that there is in this case without redesigning a large portion of code, but I just want to make sure I'm not being naive.

What are the accepted solutions for these types of problems?

Upvotes: 1

Views: 290

Answers (2)

Mattias
Mattias

Reputation: 415

Edit: this is basically Rob's answer with more characters

Rob's answer is very good. An alternative way, and please let me know if this is bad practice, is to call self.performSegueWithIdentifier (I hope I got the method name right, from the completion block of countObjectsInBackgroundWithBlock (or, as danh points out, Parse discourage count queries, use something else, maybe findObjectsInBackgroundWithBlock and count the [AnyObject]? array).

//IBAction method
func buttonFunc() { 
    let query = PFQuery(classname: "ClassName")
    query.whereKey(itemId, equalTo: self.pfObject.objectId!)
    query.findObjectsInBackgroundWithBlock {
    (objects: [AnyObject]?, error: NSError?) -> Void in 
        //maybe do some error checking etc.
        if objects.count > 1 {
        performSegueWithIdentifier(theParametersAndStuff)
        }
    }

}

If you need to pass the objects to the next ViewController you can prepareForSegue, since it will only be called if the conditional in the completion block of the buttonFunc returns true.

(as I said, if someone think this is bad practice, please tell me why, I'm curios :) )

Edit: Of course you could put a Activity Indicator or similar. An alternative way is to preload that data, before the current viewController is shown, to either enable or disable the button triggering the segue (as danh pointed out).

Upvotes: 0

Rob
Rob

Reputation: 437947

The proper solution for this is to not call anything from shouldPerformSegueWithIdentifier which won't finish very quickly (within milliseconds). If you need to perform some network request that may take a few seconds, you should:

  • Retire shouldPerformSegueWithIdentifier;

  • Remove the segue from your UI control to the next scene;

  • Instead, connect up your button to an @IBAction which will programmatically perform the asynchronous request, and only if you get the result you expected, manually perform the segue programmatically (see Switching Views Programmatically in Swift).

Upvotes: 5

Related Questions