user3353890
user3353890

Reputation: 1891

Dispatch Group not blocking code from running

I want code within a dispatch group to finish executing before anything else happens, essentially blocking the app from doing anything until this code is done. I can't get the dispatch group to block additional code from running, however. I've tried pretty much every suggestion here on stack, but I don't know what I'm doing want.

My function:

- (void)myFunction {

    NSString *myString = @"Hello world";

    dispatch_group_t group = dispatch_group_create();

    NSLog(@"1 entering the dispatch group");

    dispatch_group_enter(group);
    [self doSomething:myString completion:^{
        dispatch_group_leave(group);
        NSLog(@"2 we have left the dispatch group");
    }];
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"3 notifying that the dispatch group is finished");
    }];
    NSLog(@"4 all process are complete, we are done");
}

The output I want via log statements = 1,2,3,4

The output I get via log statements = 1,4,2,3

Why is my code skipping over the dispatch group and print 4 before 2 and 3? Any advice as to what I'm doing wrong is appreciated. Thank you!

Update:

Here is my doSomething method. My code keeps hanging on a dismiss call.

doSomething() {

    viewController.dismiss(animated: false completion: { [weak self] in 
        doMoreCode()
    })
}

Upvotes: 2

Views: 1560

Answers (2)

Rob
Rob

Reputation: 437592

Assuming doSomething runs asynchronously, you could wait for the group, but it's generally better to embrace asynchronous patterns, e.g. allow myFunction to return immediately, but supply your own completion handler to that method:

- (void)myFunctionWithCompletion:(void (^)())completion {
    NSString *myString = @"Hello world";

    dispatch_group_t group = dispatch_group_create();

    NSLog(@"1 entering the dispatch group");

    dispatch_group_enter(group);
    [self doSomething:myString completion:^{
        dispatch_group_leave(group);
        NSLog(@"2 we have left the dispatch group");
    }];
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"3 notifying that the dispatch group is finished");
    });
}

And use it like so:

[self myFunctionWithCompletion:^{
    NSLog(@"4 all process are complete, we are done");
}];

// note, it's not done when it gets here, though, because it's asynchronous

Clearly, in the above example, the dispatch group is entirely superfluous, but I assume you were doing multiple asynchronous tasks, which is why you introduced the dispatch group. If it really was just this one asynchronous task, you'd just do:

- (void)myFunctionWithCompletion:(void (^)())completion {
    NSString *myString = @"Hello world";

    [self doSomething:myString completion:^{
        NSLog(@"The asynchronous doSomething is done");
        completion();
    }];
}

Upvotes: 1

Rob Napier
Rob Napier

Reputation: 299345

Nothing here actually blocks. dispatch_group_notify just says "when the group finishes, run this." The tool you meant to use was dispatch_group_wait. If you want 1,2,3,4, then you meant this:

- (void)myFunction {

    NSString *myString = @"Hello world";

    dispatch_group_t group = dispatch_group_create();

    NSLog(@"1 entering the dispatch group");

    dispatch_group_enter(group);
    [self doSomething:myString completion:^{
        NSLog(@"2 we have left the dispatch group");
        dispatch_group_leave(group);
    }];

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"3 notifying that the dispatch group is finished");

    NSLog(@"4 all process are complete, we are done");
}

myFunction of course cannot be called on the main queue in iOS (since it blocks and you must never block the main queue). And it also must not be called on the same queue that doSomething:completion: uses for its completion handler (since that queue will be blocked at dispatch_group_wait).

Remember that dispatch_queue_notify just adds a block to a queue to be run sometime in the future. So it's a little unclear how you expect 3 and 4 to work (in my example I just collapsed them, but maybe you're looking for something else).

Another approach is to not block the application, and just schedule stuff to run when it's supposed to. In that case you can use the main queue. It'd look like this:

- (void)myFunction {

    NSString *myString = @"Hello world";

    dispatch_group_t group = dispatch_group_create();

    NSLog(@"1 entering the dispatch group");

    dispatch_group_enter(group);
    [self doSomething:myString completion:^{
        NSLog(@"2 we have left the dispatch group");
        dispatch_group_leave(group);
    }];
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"3 notifying that the dispatch group is finished");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"4 all process are complete, we are done");
    });
}

Note in both examples, I'm logging 2 before calling dispatch_group_leave. In this example, I'm also registering two things to be run on the main queue (in order) after the group is done. In this case, myFunction will return immediately (so it can be run on the main queue), but everything should print out in order.

Upvotes: 3

Related Questions