3

I am new to blocks and am trying to figure out how to wait for the block to finish before performing my action (in this case a nslog) So how can I wait till the block is done before performing this nslog in the code below: NSLog(@"convertedPhotos::%@",convertedImages);

          convertedImages =  [[NSMutableArray alloc] init];
        for (NSDictionary *photo in photos) {
            // photo is a dictionary containing a "caption" and a "urlRep"
            [photoUrls addObject:photo[@"urlRep"]];
        }

        if (photoUrls.count) {
            for (id photos in photoUrls){
                NSString *urlString = photos;
                [self base64ImageAtUrlString:urlString result:^(NSString *base64) {


                    [jsonWithPhotos setObject:convertedImages forKey:@"photo64"];
                    NSError *error;
                    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonWithPhotos
                                                                       options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
                                                                         error:&error];

                    if (! jsonData) {
                        NSLog(@"Got an error: %@", error);
                    } else {
                        NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
                        NSLog(@"json::%@",jsonString);

                    }
                }];
            }
        }
        else {
            NSLog(@"where are my urls?");
        }
        NSLog(@"convertedPhotos::%@",convertedImages);

    }

}

this method/block is called from above

- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
    NSURL *url = [NSURL URLWithString:urlString];
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

    [library assetForURL:url resultBlock:^(ALAsset *asset) {

        // borrowing your code, here... didn't check it....
        ALAssetRepresentation *representation = [asset defaultRepresentation];
        CGImageRef imageRef = [representation fullResolutionImage];

        //TODO: Deal with JPG or PNG
        NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
        NSString *base64 = [imageData base64EncodedString];
        completion(base64);
        [convertedImages addObject:base64];

//        NSLog(@"converted::%@",convertedImages);

    } failureBlock:^(NSError *error) {
        NSLog(@"that didn't work %@", error);
    }];
}
BluGeni
  • 3,378
  • 8
  • 36
  • 64

3 Answers3

6

I would use NSOperationQueue (or dispatch queue) and NSCondition (or dispatch group) to wait for operations to complete. It is also important to wrap blocks in @autoreleasepool to flush memory once you do not need it if you work with memory consuming objects like NSData.

example:

// create results array
__block NSMutableArray* results = [NSMutableArray new];

// create serial queue
dispatch_queue_t queue = dispatch_queue_create("myQueue", 0);

for(NSInteger i = 0; i < 10; i++) {
    // enqueue operation in queue
    dispatch_async(queue, ^{
        // create semaphore
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);

        // do something async, I do use another dispatch_queue for example
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            // wrap in autoreleasepool to release memory upon completion
            // in your case wrap the resultBlock in autoreleasepool
            @autoreleasepool {
                // here for example the nested operation sleeps for two seconds
                sleep(2);

                // add the operation result to array
                // I construct an array of strings for example
                [results addObject:[NSString stringWithFormat:@"Operation %d has finished.", i]];

                // signal that nested async operation completed
                // to wake up dispatch_semaphore_wait below
                dispatch_semaphore_signal(sema);
            }
        });

        // wait until the nested async operation signals that its finished
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

        NSLog(@"Finished single operation.");
    });
}

// will be called once all operations complete
dispatch_async(queue, ^{
    NSLog(@"Finished all jobs.");
    NSLog(@"Results: %@", results);
});
pronebird
  • 12,068
  • 5
  • 54
  • 82
  • @BlueGeni I updated my answer again to use semaphores instead of groups as it is more appropriate here. – pronebird Jan 11 '14 at 00:17
4

For any non-main queue use semaphores

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // some serious stuff here
    ...        
    dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

In case you want to wait for async task execution being in the main queue - you wouldn't probably want to block it while waiting for a semaphore. I use this construction which doesn't freeze the UI for the main queue only.

__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // some serious stuff here
    ...        
    flag = YES;
});
// Run until 'flag' is not flagged (wait for the completion block to finish executing
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
Vladimir Afinello
  • 1,211
  • 14
  • 16
  • This worked for me. This approach is useful in case async call is using UI thread and you don't want to freeze the UI – Umair Sep 14 '21 at 17:51
2

Your best option is not to use blocks directly. Instead, create instances of NSBlockOperation and add them to an operation queue. Then create one more NSBlockOperation and make it dependent upon all of the other operations. This ensures that the last operation is only run after all others are completed and allows you to control how many operations will run at any one time.

If you have nested block calls, or some API that you can't change to enable this then you can still do it if you create an NSOperation subclass such that the operation does not complete until all of the asynchronous operations are complete. Take a look at dribin.org concurrent operations.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • is this what you are talking about? http://stackoverflow.com/questions/4326350/how-do-i-wait-for-an-asynchronously-dispatched-block-to-finish – BluGeni Jan 06 '14 at 14:57
  • You could try a semaphore based solution but you still need to know which is last (so it should signal) or to have a counting semaphore (so all are completed before it is released). – Wain Jan 06 '14 at 15:00