4

I want to synchronize some data with a web service. For each item I have to make a asynchronous call.

I want to have a completion block witch is called, when each item was synchronized. For each item I am able to perform a completion block. Now, I don't know a good way how to do it.

This is the interface:

-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
    NSArray* items = // get items
    for (int i = 0, n = [items count]; i < n; i++) {
       [self synchronizeItem:[items objectAtIndex:i] onComplete:^{
          // What do do here?
       }];
    }
    // And/or here?
}

-(void) synchronizeItemOnComplete:(CompleteBlock) block {
    // do something
    block();
}

How can I wait for the synchronization and then perform the block?

I tried something like this:

NSArray* items = // get items

__block int countOfItemsUntilDone = [items count];

for (int i = 0, n = countOfItemsUntilDone; i < n; i++) {
    [self synchronizeItem:[items objectAtIndex:i] onComplete:^{
        countOfItemsUntilDone--;
    }];
}

dispatch_queue_t queue = dispatch_queue_create("wait for syncing", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    while (countOfItemsUntilDone > 0) {
        usleep(1000); // wait a little bit
    }
    block();
});
dispatch_release(queue);

But I think this is a quite bad way. Any ideas?

Obenland
  • 856
  • 16
  • 28

3 Answers3

4

Instead of spinning in a loop waiting for the counter to equal zero, check the counter value each time you decrement it, then fire an event when it reaches zero.

 -(void) synchronizeItemsOnComplete:(CompleteBlock) block {
    NSArray* items = // get items
    __block NSUInteger remaining = [items count];

    for (ItemClass* item in items) {
       [self synchronizeItemImage:item onComplete:^{
            --remaining;
            if (remaining == 0) {
                block();
            }
       }];
    }
 }

To explain why it feels wrong, there are two things you're doing here that you should do either never or rarely:

  • Using background queues. This is difficult and bug-prone. Don't do it without reading up a lot about writing concurrent code. You also only really need to do this if an operation blocks for a substantial amount of time (eg., to read a file from disk, or perform an intensive calculation). Don't assume you need to do it unless you have a good reason (eg., a measurable performance problem).

  • Spinning in a loop, checking a variable for changes and calling sleep. You should never do this.

Also, if you're looping over the elements in an array, the for ... in syntax is much nicer (and potentially more efficient) calling objectAtIndex: on each index.

Chris Devereux
  • 5,453
  • 1
  • 26
  • 32
2

Never check or decrement shared memory in different threads like this, it can cause races. Use a dispatch group to do what you're doing.

dispatch_queue_t myBGQueue;
dispatch_group_t itemsGroup = dispatch_group_create();

for (ItemClass *item in items) {
    dispatch_group_async(itemsGroup, myBGQueue, ^{
        [self synchronizeItemImage:item];
    });
}

/* execution will sleep here until all the blocks added in the `for` complete */
dispatch_group_wait(itemsGroup, DISPATCH_TIME_FOREVER);

dispatch_release(itemsGroup);
iluvcapra
  • 9,436
  • 2
  • 30
  • 32
  • Oh... I didn't make that point clear. That called synchronizeItemImage method starts a asynchronous request via RESTKit. So, to perform that method async or with `waitUntilDone:` probably wouldn't work. To avoid decrementing shared memory in different threads I could execute the block from Chris Devereux's solution on a serial dispatch queue, or is this bad, too? – Obenland Feb 25 '13 at 18:56
  • Oh. In that case, as long as the completion handlers are called on the main thread you can just decrement some variable. Or better, decrement a property on your windowController or application delegate and use KVO notifications to find out when it hits zero. – iluvcapra Feb 25 '13 at 19:28
1

You can use these to use synchronously.

GCD and this

performSelector:waitUntilDone:YES

Anoop Vaidya
  • 46,283
  • 15
  • 111
  • 140