Waiting for multiple blocks to finish

You were almost there, the problem is most likely to be that those methods are asynchronous, so you need an extra synchronization step. Just try with the following fix:

for(Appliance *appliance in _mutAppliances) {
  dispatch_group_async(
     group,
     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       dispatch_semaphore_t sem = dispatch_semaphore_create( 0 );

       NSLog(@"Block START");

       [appliance downloadAppInfo:^{
          NSLog(@"Block SUCCESS");
            dispatch_semaphore_signal(sem);
       }
       failure:^(NSError *error){
         NSLog(@"Block FAILURE");
         dispatch_semaphore_signal(sem);
       }];

       dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

       NSLog(@"Block END");
 });

 dispatch_group_notify(
   group,
   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
     NSLog(@"FINAL block");
     success();
 });
}

Drawn from the comments in other answers here, and the blog post Using dispatch groups to wait for multiple web services, I arrived at the following answer.

This solution uses dispatch_group_enter and dispatch_group_leave to determine when each intermediate task is running. When all tasks have finished, the final dispatch_group_notify block is called. You can then call your completion block, knowing that all intermediate tasks have finished.

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {

    // ...

    dispatch_group_leave(group);
}];

dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {

    // ...

    dispatch_group_leave(group);
}];

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

    // All group blocks have now completed

    if (completion) {
        completion();
    }
});

Grand Central Dispatch - Dispatch Groups

https://developer.apple.com/documentation/dispatch/dispatchgroup

Grouping blocks allows for aggregate synchronization. Your application can submit multiple blocks and track when they all complete, even though they might run on different queues. This behavior can be helpful when progress can’t be made until all of the specified tasks are complete.

Xcode Snippet:

I find myself using Dispatch Groups enough that I've added the following code as an Xcode Snippet for easy insertion into my code.

Now I type DISPATCH_SET and the following code is inserted. You then copy and paste an enter/leave for each of your async blocks.

Objective-C:

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);

dispatch_group_leave(group);

dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

});

Swift:

let dispatchGroup = DispatchGroup()

dispatchGroup.enter()

dispatchGroup.leave()

dispatchGroup.notify(queue: .global()) {

}