24

I would like to wait this code to be executed before to continue but as these blocks are called assynchronously I don't know how to do???

NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];

ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
    asseturl = [NSURL URLWithString:[dico objectForKey:@"assetUrl"]];
    NSLog(@"asset url %@", asseturl);
    // Try to load asset at mediaURL
    [library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
        // If asset doesn't exists
        if (!asset){
            [objectsToRemove addObject:dico];
        }else{
            [tmpListAsset addObject:[asseturl absoluteString]];
            NSLog(@"tmpListAsset : %@", tmpListAsset);
        }
    } failureBlock:^(NSError *error) {
        // Type your code here for failure (when user doesn't allow location in your app)
    }];
}
Mathieu
  • 1,165
  • 4
  • 18
  • 34

5 Answers5

44

GCD semaphore approach:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

for (NSURL *url in self.assetUrls) {
    dispatch_async(queue, ^{
        [library assetForURL:url resultBlock:^(ALAsset *asset) {
            [self.assets addObject:asset];
            dispatch_semaphore_signal(sema);
        } failureBlock:^(NSError *error) {
            dispatch_semaphore_signal(sema);
        }];
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);

/* Check out ALAssets */
NSLog(@"%@", self.assets);
Cœur
  • 32,421
  • 21
  • 173
  • 232
Chad Podoski
  • 958
  • 9
  • 11
  • 4
    note that if this code should not be called in the background thread (e.g: using `performSelectorInBackground:`), since a deadlock will occur (the blocks seem to be called on the same thread, so the semaphore is never signaled). – alex-i Sep 26 '13 at 14:44
  • 1
    This should be the selected answer @Mathieu – brandonscript Apr 16 '15 at 17:21
6

The solution is here http://omegadelta.net/2011/05/10/how-to-wait-for-ios-methods-with-completion-blocks-to-finish/

Mathieu
  • 1,165
  • 4
  • 18
  • 34
  • 2
    This is a link-only answer and should be improved by both including the relevant code from the source article, and modernization (since this answer is now 4 years old). – brandonscript Apr 15 '15 at 18:23
4

Note that assetForURL:resultBlock:failureBlock: will stuck if the main thread is waiting without RunLoop running. This is alternative ( cleaner :-) ) solution:

#import <libkern/OSAtomic.h>

...

ALAssetsLibrary *library;
NSMutableArray *assets;
...
__block int32_t counter = 0;
for (NSURL *url in urls) {
    OSAtomicIncrement32(&counter);
    [library assetForURL:url resultBlock:^(ALAsset *asset) {
        if (asset)
            [assets addObject:asset];
        OSAtomicDecrement32(&counter);
    } failureBlock:^(NSError *error) {
        OSAtomicDecrement32(&counter);
    }];
}
while (counter > 0) {
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
Johnny Wong
  • 855
  • 7
  • 16
  • 1
    Found that my method is not always work (infinite wait may happen). I suggest shacked's GCD semaphore method which works for me. – Johnny Wong Jul 05 '12 at 09:12
1

This is an easy way to do it. Maybe not as elegant as using GCD but it should get the job done ... This will make your method blocking instead of non-blocking.

__block BOOL isFinished = NO;
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];

ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
    asseturl = [NSURL URLWithString:[dico objectForKey:@"assetUrl"]];
    NSLog(@"asset url %@", asseturl);
    // Try to load asset at mediaURL
    [library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
        // If asset doesn't exists
        if (!asset){
            [objectsToRemove addObject:dico];
        }else{
            [tmpListAsset addObject:[asseturl absoluteString]];
            NSLog(@"tmpListAsset : %@", tmpListAsset);
        }
        if (objectsToRemove.count + tmpListAsset.count == assetsList.count) {
            isFinished = YES;
        }
    } failureBlock:^(NSError *error) {
        // Type your code here for failure (when user doesn't allow location in your app)
        isFinished = YES;
    }];
}

while (!isFinished) {
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
}
Adam Freeman
  • 1,171
  • 9
  • 18
1

The easiest thing to do is to move your code to inside (at the end of) the resultBlock or the failureBlock. That way, your code will run in the correct order, and you will also retain asynchronous behaviour.

Chaitanya Gupta
  • 3,983
  • 2
  • 27
  • 41
  • You mean after the else } The thing is I need the loop to be finished and also the code that is following is a a large piece of code with other async functions... – Mathieu Aug 29 '11 at 20:26
  • Assuming this is running on the main thread, do you really want to block the whole main thread while waiting for this block to finish? – Chaitanya Gupta Aug 30 '11 at 02:09