如何让循环等到块完成?

How to make loop wait until block has finished?

我从解析中检索了一个 PFFiles 数组。 PFFiles 必须转换为图像,但是我正在尝试循环转换它们;

转换图像数组的顺序必须与包含 PFFiles 的数组的顺序相同。

问题是,循环运行并导致所有块触发,然后它们都同时加载,因此 imageArray 将导致与 [ 的原始数组不同的顺序=11=], 因为如果一个对象在前一个对象之前完成加载,它将在前一个对象之前添加到数组中,使其乱序。

相反,我想要一个循环,它循环遍历数组中的每个对象,但是它不会循环到下一个对象,直到 getDataInBackgroundBlock 完成加载并且当前对象已添加到新数组。

-(void)organizePhotos {
    for (PFFile *picture in pictureArray) {
        //This block loads, after the next index in the loop is called, causing the array to be out of order.
        [picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
            [imageArray addObject:[UIImage imageWithData:data]];
            [self savePhotos];
        }];
    }
}

以上是我的代码。无论如何我可以让循环等到 getDatanBackgroundWithBlock 完成加载吗?

你可以使用GCD方法,也就是使用dispatch_group。所以,在你开始一个异步任务之前,调用dispatch_group_enter,然后当异步任务完成时,调用dispatch_group_leave,然后你可以创建一个dispatch_group_notify,它会在异步任务完成。您可以将它与完成块模式结合起来(无论如何,这对于异步方法来说是个好主意):

这是类似的问题。也许它也可能有帮助: How to wait for method that has completion block (all on main thread)?

理想情况下,您应该使用同步方法;然而,您所要求的行为可以使用 Grand Central Dispatch 实现。

首先,使用 dispatch_group_create() 创建一个 dispatch_group_t。我们称它为 asyncGroup.

然后,在调用异步方法之前,调用dispatch_group_enter(asyncGroup)。这会增加组中呼叫次数的计数器。

然后,在异步块的末尾,调用 dispatch_group_leave(asyncGroup) 来减少组中调用次数的计数器。

最后,调用异步方法后,调用dispatch_group_wait(asyncGroup, timeout)暂停线程执行,直到组计数器归零。由于您递增,进行调用,然后等待,因此只有在异步块已 运行 时,循环才会继续。只需确保超时时间长于操作所需的时间即可。

您可以使代码同步:

-(void)organizePhotos {
    for (PFFile *picture in pictureArray) {
         [imageArray addObject:[UIImage imageWithData:[picture getData]]];
        [self savePhotos];
    }
}

和运行它在后台:

[self performSelectorInBackground:@selector(organizePhotos) withObject:nil];

您可以使用 dispatch_async 方式、使用信号量、组来做到这一点,但为了您的需要,更好的做法是使用块来获得响应并执行您需要的操作。

这样做:

-(void)organizePhotos:(void(^)(BOOL responseStatus))status {
for (PFFile *picture in pictureArray) {
    //This block loads, after the next index in the loop is called, causing the array to be out of order.
    [picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
        [imageArray addObject:[UIImage imageWithData:data]];
        status(YES);
    }];
}

}

因此,您接下来可以这样做:

__weak typeof(self) weakSelf = self;
[self organizePhotos:^(BOOL responseStatus) {
 if(responseStatus){
       [weakSelf savePhotos];
 }   
}];

或者,如果您不想为您的方法创建额外的参数响应,您可以这样做:

-(void)organizePhotos {

__weak typeof(self) weakSelf = self;
void (^ResponseBlock)(void) = ^{
     [weakSelf savePhotos];
};

for (PFFile *picture in pictureArray) {
    //This block loads, after the next index in the loop is called, causing the array to be out of order.
    [picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
        [imageArray addObject:[UIImage imageWithData:data]];
        ResponseBlock();
    }];
}

}

还有一点,你在块内调用 "self" 时犯了错误,这可能会导致你保留循环,这是一种不好的做法,看看我的代码你应该怎么做。

The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time

那是不是问题,这很好 - 调用是异步的原因是它可能需要任意长的时间才能完成。如果您有多个下载要做,那么同时进行可能是一个巨大的胜利。

thus the imageArray will result in a different order to the original array of PFFiles, because if one object finishes loading before the previous object, it will be added to the array before the previous one, making it go out of order.

这就是问题所在,可以通过多种方式解决。

由于您正在使用数组,这里有一个简单的基于数组的解决方案:首先为自己创建一个大小合适的数组并用空值填充它以指示图像尚未到达:

(所有直接输入答案的代码,视为伪代码并期待一些错误)

NSUInteger numberOfImages = pictureArray.length;

NSMutableArray *downloadedImages = [NSMutableArray arrayWithCapacity:numberOfImages];
// You cannot set a specific element if it is past the end of an array
// so pre-fill the array with nulls
NSUInteger count = numberOfImages;
while (count-- > 0)
   [downloadedImages addObject:[NSNull null]];

现在你有了预填充的数组,只需修改现有循环以将下载的图像写入正确的索引即可:

for (NSUInteger ix = 0; ix < numberOfImages; ix++)
{
    PFFile *picture = pictureArray[ix];
    [picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
        dispatch_async(dispatch_get_main_queue(),
                       ^{ [imageArray replaceObjectAtIndex:ix
                                                withObject:[UIImage imageWithData:data]
                        });
        [self savePhotos];
    }];
}

这里使用dispatch_async是为了保证数组没有并发更新。

如果您想知道所有图像何时下载完毕,您可以在 dispatch_async 块中进行检查,例如它可以安全地增加一个计数器,因为它在主线程上是 运行 并在所有图像下载后调用一个 method/issue 一个 notification/invoke 块。

您还可能通过使用数组并尝试让不同数组中的项目按位置相关而让事情变得更难。字典可以为您省去一些麻烦,例如,您的每个 PFFile 对象可能与不同的 URL 相关,而 URL 是字典的完全有效键。所以您可以执行以下操作:

NSMutableDictionary *imageDict = [NSMutableDictionary new];

for (PFFile *picture in pictureArray) {
    [picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
        dispatch_async(dispatch_get_main_queue(),
                       ^{ [imageDict setObject:[UIImage imageWithData:data] forKey:picture.URL];
                        };
        [self savePhotos];
    }];
}

并且您可以通过在字典中查找其 URL 来找到 PFFile 实例的图像 - 如果图像尚未找到,它将 return nil已加载。

还有其他解决方案,您可能需要考虑让您的代码更加异步。无论您做什么尝试同步调用异步代码都不是一个好主意,并且会影响用户体验。

HTH