1

I'm trying to wait for Parse async functions in Swift to reload my UITableView

I'm not sure if Completion Handler is useful in this case. or Dispatch Async.

I'm really confused ! Can someone help out with this

var posts = [PFObject]()
for post in posts {
    post.fetchInBackground()
}
tableView.reloadData() // I want to execute that when the async functions have finished execution

5 Answers 5

2
+50

You want to use fetchAllInBackground:Block I've had issues launching a bunch of parse calls in a loop where it will take a lot longer to return all of them than expected. fetch documentation

It should look something like this:

PFObject.fetchAllInBackground(posts, block: { (complete, error) in
    if (error == nil && complete) {
        self.tableView.reloadData()
    }
})

One thing to note is that in your example posts are empty and a generic PFObject. I'm assuming this is just for the example. Otherwise if you want to get all posts in Parse (as opposed to updating current ones) you will want to use PFQuery instead of fetching. query documentation

Sign up to request clarification or add additional context in comments.

Comments

1

You need to use fetchInBackgroundWithBlock. Alternatively, if you want to wait until all have loaded and then update the UI, use PFObject's +fetchAllInBackground:block:. Note that this is a class method, and would therefore be called as PFObject.fetchAllInBackground(.... See documentation here.

Either way, because you're running in a background thread, you must update the UI on the main thread. This is normally done using dispatch_async.

The other thing to watch out for is if you run fetchInBackgroundWithBlock in a loop and collect all the results in an array, arrays are not thread safe. You will have to use something like dispatch_barrier or your own synchronous queue to synchronise access to the array. Code for the second option is below:

// Declared once and shared by each call (set your own name)...
let queue = dispatch_queue_create("my.own.queue", nil)

// For each call...
dispatch_sync(queue) {
    self.myArray.append(myElement)
}

6 Comments

Even If I use fetchInBackgroundWithBlock it won't help as long as I want all them to be completed to update my UI data
Really? Normally you progressively update the UI as each one loads.
the way I am using it I do not need to dispatch self.tableView.reloadData() to the main queue. I am calling fetchData() from my viewDidLoad() func so perhaps if you are calling it from elsewhere you will need to dispatch self.tableView.reloadData to the main queue
There is no fetchAllinBackground, can you please give an example of dispatch_barrier
fetchAllInBackground came from the documentation - I will include the link.(note that it's capital-i)
|
1

Here's a little class I made to help with coordination of asynchronous processes:

class CompletionBlock
{
  var completionCode:()->()

  init?(_ execute:()->() )
  { completionCode = execute }

  func deferred() {}

  deinit
  { completionCode() }
}

The trick is to create an instance of CompletionBlock with the code you want to execute after the last asynchronous block and make a reference to the object inside the closures.

let reloadTable = CompletionBlock({ self.tableView.reloadData() })
var posts = [PFObject]()
for post in posts 
{
    post.fetchInBackground(){ reloadTable.deferred() }
}

The object will remain "alive" until the last capture goes out of scope. Then the object itself will go out of scope and its deinit will be called executing your finalization code at that point.

Comments

0

Here is an example of using fetchInBackgroundWithBlock which reloads a tableView upon completion

    var myArray = [String]()
func fetchData() {
    let userQuery: PFQuery = PFUser.query()!

    userQuery.findObjectsInBackgroundWithBlock({
        (users, error) -> Void in

        var userData = users!

        if error == nil {
            if userData.count >= 1 {
                for i in 0...users!.count-1 {
                    self.myArray.append(userData[i].valueForKey("dataColumnInParse") as! String)
                }
            }
            self.tableView.reloadData()
        } else {
            print(error)
        }
    })
}

My example is a query on the user class but you get the idea...

3 Comments

Is it guaranteed that the completion handler will execute on the main thread?
I am unsure if it is guaranteed.
If the completion handler executes on any other thread than the main thread, then your solution may crash: the table view tries to access self.myArray in order to render the cells, while the same array is modified by another thread.
0

I have experimented a bit with the blocks and they seem to get called on the main thread, which means that any UI changes can be made there. The code I have used to test looks something like this:

func reloadPosts() {
  PFObject.fetchAllIfNeededInBackground(posts) { 
    [unowned self] (result, error) in

    if let err = error {
      self.displayError(err)
    } 

    self.tableView.reloadData()
  }
}

if you are in doubt about whether or not the block is called on the main thread you can use the NSThread class to check for this

print(NSThread.currentThread().isMainThread)

And if you want it to be bulletproof you can wrap your reloadData inside dispatch_block_tto ensure it is on the main thread

Edit: The documentation doesn't state anywhere if the block is executed on the main thread, but the source code is pretty clear that it does

+ (void)fetchAllIfNeededInBackground:(NSArray *)objects block:(PFArrayResultBlock)block {
    [[self fetchAllIfNeededInBackground:objects] thenCallBackOnMainThreadAsync:block];
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.