Waiting for code to execute (already used dispatch groups)

138 Views Asked by At

Got this lengthy section of code shown below and I've hit a brick wall. Basically the code runs perfectly and does exactly what I want it to do. However, it needs to finish running all the code within this section before printing the "Finished" at the end. However adding semaphores or another dispatch group forces a breakpoint. Might be obvious, but could someone give me a bit of advice on this please?

Note: I cannot use that dispatch at the bottom to call another method. Remember its within a loop.

for (id i in arr) {
    searchByName = nil;
    if ([i containsString:@"word1"] || [i containsString:@"word2"]) {
        NSRange searchFromRange = [i rangeOfString:@"ong>"];
        NSRange searchToRange = [i rangeOfString:@"</str"];
        NSString *substring = [i substringWithRange:NSMakeRange(searchFromRange.location+searchFromRange.length, searchToRange.location-searchFromRange.location-searchFromRange.length)];
        [allergens addObject:substring];
        if ([substring isEqualToString:@"Examee"] && veg_lac_ovoSafe == TRUE) {
            veg_ovoSafe = FALSE;
            vegSafe = FALSE;
        }
        else if ([substring isEqualToString:@"Example"] && veg_lac_ovoSafe == TRUE) { //USE OF HEURISTICS
            veg_lacSafe = FALSE;
            vegSafe = FALSE;
        }
        else if ([substring isEqualToString:@"Exam"]) {
            pescetarianSafe = TRUE;
            vegSafe = FALSE;
            veg_ovoSafe = FALSE;
            veg_lacSafe = FALSE;
            veg_lac_ovoSafe = FALSE;
            pollotarianSafe = FALSE;
        }
        NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
        NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
        substring = [[substring componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:@""];
        searchByName = [[[substring componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:@""] lowercaseString];
    }
    else {
        NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
        NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
        NSString *searchItem = [[i componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:@""];
        searchByName = [[[searchItem componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:@""] lowercaseString];
    }
    if (![searchByName isEqualToString:@" "]) {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
        dispatch_group_enter(_groupSearch);
        dispatch_async(queue, ^{
            [[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded
                                                      withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
                if (snapshot.value != NULL) {
                    NSLog(@"%@", snapshot.value);
                    for (int i=0; i < [[NSString stringWithFormat:@"%@", snapshot.value] length]; i++) {
                        NSString *x  = [NSString stringWithFormat:@"%c", [[NSString stringWithFormat:@"%@", snapshot.value] characterAtIndex:i]];
                        NSLog(@"%@", x);
                        if ([x isEqualToString:@"1"]) {
                            vegSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"2"]) {
                            vegSafe = FALSE;
                            veg_lacSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"3"]) {
                            vegSafe = FALSE;
                            veg_ovoSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"4"]) { //Could use switch case.
                            vegSafe = FALSE;
                            veg_lac_ovoSafe = FALSE;
                            veg_lacSafe = FALSE;
                            veg_ovoSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"5"]) {
                            pescetarianSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"6"]) {
                            pollotarianSafe = FALSE;
                        }
                    }
                }

                dispatch_group_leave(_groupSearch);
            }
                                                withCancelBlock:^(NSError * _Nonnull error) {
                NSLog(@"%@", error.localizedDescription);
                dispatch_group_leave(_groupSearch);
            }];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
            dispatch_sync(queue, ^{
                //Hi
            });
        });
    }
}

NSLog(@"Finished");
2

There are 2 best solutions below

12
On BEST ANSWER

Your use of dispatch_group_wait is in the wrong place. It needs to after the for loop, not inside it. And you can create queue before the loop and reuse it as needed.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

for (id i in arr) {
    searchByName = nil;

    // Lots of other code here

    if (![searchByName isEqualToString:@" "]) {
        dispatch_group_enter(_groupSearch);
        dispatch_async(queue, ^{
            [[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
                if (snapshot.value != NULL) {
                    NSLog(@"%@", snapshot.value);
                    for (int i=0; i < [[NSString stringWithFormat:@"%@", snapshot.value] length]; i++) {
                        NSString *x  = [NSString stringWithFormat:@"%c", [[NSString stringWithFormat:@"%@", snapshot.value] characterAtIndex:i]];
                        NSLog(@"%@", x);
                        if ([x isEqualToString:@"1"]) {
                            vegSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"2"]) {
                            vegSafe = FALSE;
                            veg_lacSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"3"]) {
                            vegSafe = FALSE;
                            veg_ovoSafe = FALSE;

                        }
                        else if ([x isEqualToString:@"4"]) { //Could use switch case.
                            vegSafe = FALSE;
                            veg_lac_ovoSafe = FALSE;
                            veg_lacSafe = FALSE;
                            veg_ovoSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"5"]) {
                            pescetarianSafe = FALSE;
                        }
                        else if ([x isEqualToString:@"6"]) {
                            pollotarianSafe = FALSE;
                        }
                    }
                }
                dispatch_group_leave(_groupSearch);
            }
            withCancelBlock:^(NSError * _Nonnull error) {
                NSLog(@"%@", error.localizedDescription);
                dispatch_group_leave(_groupSearch);
            }];

        });
    }
}

dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));

NSLog(@"Finished");
0
On

Since you're calling dispatch_group_wait() inside a dispatch_async() block, it appears you want to run the completion block asynchronously on the global dispatch queue. The proper way to do this is to use dispatch_group_notify() instead:

dispatch_group_notify(groupSearch, queue, ^{
    NSLog(@"Finished")
});

This will run the block on the specified queue once all the blocks submitted to the group have finished, so no dispatch_async() or dispatch_sync() is needed. Additionally, waiting inside dispatch_async() will block a thread in the GCD pool, which is a really bad idea since there are a finite number of them. This is also something that using dispatch_group_notify() instead will avoid.

EDIT: The paragraph below is no longer relevant to your updated code:

Also, as the other answerer pointed out, you probably want to put the call to dispatch_group_notify() after the loop finishes, assuming that you want one completion block to run at the very end of your work. If what you want actually is a whole bunch of separate notifications, one for each run through the loop, then you should probably create a new group within the loop and use that instead. Using one group for the whole shebang and then setting up notifications within the loop is going to cause each notification to wait not only for the dispatch_group_leave() calls for that particular run through the loop, but for all the dispatch_group_enter() calls that have been made on any run through the loop to be balanced. So you'll get nothing until all your work is done, and then you'll suddenly spam a whole bunch of completion blocks all at once, with an accompanying thread explosion since they'll all be submitted on the global queue. This is probably not what you want.