等待assetForURL块完成

24

在继续之前,我希望等待此代码执行,但由于这些块是异步调用的,我不知道该怎么做?

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)
    }];
}
5个回答

44

GCD信号量方法:

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);

4
请注意,如果在后台线程中调用此代码(例如:使用performSelectorInBackground:),将会发生死锁(因为块被似乎在同一线程上调用,所以信号量永远不会被触发)。请注意避免这种情况。 - alex-i
1
这应该是被选中的答案 @Mathieu - brandonscript

6

2
这只是一个链接回答,应该通过包含源文章中相关的代码以及现代化(因为此回答已经四年了)来改进。 - brandonscript

4
请注意,如果主线程等待而没有运行RunLoop,则assetForURL:resultBlock:failureBlock:将被卡住。这是一种替代(更清晰 :-))解决方案:
#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]];
}

1
发现我的方法并不总是有效(可能会出现无限等待的情况)。我建议使用 shacked 的 GCD 信号量方法,这对我很有效。 - Johnny Wong

1
这是一种简单的方法。也许不如使用GCD那样优雅,但它应该能完成工作...这将使您的方法阻塞而不是非阻塞。
__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]];
}

1
最简单的方法是将您的代码移动到resultBlockfailureBlock内部(末尾)。这样,您的代码将按正确顺序运行,并且还将保留异步行为。

你的意思是在 else } 之后吗? 问题是我需要循环结束,而且接下来的代码是一个包含其他异步函数的大段代码... - Mathieu
假设这是在主线程上运行,你真的想要在等待此块完成时阻塞整个主线程吗? - Chaitanya Gupta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接