【问题标题】:How to make loop wait until block has finished?如何让循环等到块完成?
【发布时间】:2015-11-16 18:22:36
【问题描述】:

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

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

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

相反,我想要一个循环,它循环遍历数组中的每个对象,但是在 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 完成加载?

【问题讨论】:

  • 除了 NickEntin 的建议之外,如果你想使用干净的 Objective-C 解决方案,你可以使用依赖操作。

标签: ios objective-c arrays loops block


【解决方案1】:

问题是,循环运行并导致所有块触发,然后它们都同时加载

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

因此 imageArray 将导致与原始 PFFiles 数组不同的顺序,因为如果一个对象在前一个对象之前完成加载,它将在前一个对象之前添加到数组中,从而使其无序。

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

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

(所有代码直接输入到答案中,视为伪代码并预计会出现一些错误)

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 块中进行检查,例如当它在主线程上运行时,它可以安全地增加一个计数器,并在所有图像都下载后调用方法/发出通知/调用块。

您还可能通过使用数组来让自己变得更难,并尝试将项目保存在按位置相关的不同数组中。字典可以为您节省一些麻烦,例如,您的每个 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 实例的图像 - 如果图像尚未加载,它将返回 nil

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

HTH

【讨论】:

    【解决方案2】:

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

    首先,使用dispatch_group_create() 创建一个dispatch_group_t。我们就叫它asyncGroup吧。

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

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

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

    【讨论】:

      【解决方案3】:

      您可以使代码同步:

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

      并在后台运行它:

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

      【讨论】:

        【解决方案4】:

        您可以使用 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)?

        【讨论】:

          【解决方案5】:

          您可以使用 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”时犯了错误,这可能会导致你保留循环,这是一种不好的做法,看看我的代码你应该怎么做。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-11-10
            • 1970-01-01
            • 1970-01-01
            • 2019-04-15
            相关资源
            最近更新 更多