从迭代NSMutableArray中删除对象

时间:2012-04-05 04:26:36

标签: objective-c ios

根据Best way to remove from NSMutableArray while iterating?,我们无法在迭代时从NSMutableArray中删除对象,是的。

但是,如果我有如下代码

,该怎么办?
- (void)sendFeedback {
    NSMutableArray *sentFeedback = [NSMutableArray array];
    for (NSMutableDictionary *feedback in self.feedbackQueue){
        NSURL *url = [NSURL URLWithString:@"someApiUrl"];
        ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
        [request setPostValue:[feedback objectForKey:@"data"] forKey:@"data"];
        [request setCompletionBlock:^{
            [sentFeedback addObject:feedback];
        }];
        [request startAsynchronous];
    }
    [self.feedbackQueue removeObjectsInArray:sentFeedback];
}

我正在使用NSRunLoop来创建NSThread,以便每隔一段时间执行sendFeedback方法。我将数据发送到API的方式是使用异步方法(将为每个请求创建后台线程)一旦发送了反馈,就必须在NSRunner执行此方法之前将其删除。下一期以避免重复提交数据。

通过使用异步,循环(主线程)将继续运行,而无需等待服务器的响应。在某些情况下(可能是大多数情况),循环将在每个请求的服务器的所有响应返回之前完成运行。如果是这样,完成块的代码将在 removeObjectsInArray 之后执行,这将导致发送的数据保留在self.feedbackQueue

我很确定有几种方法可以避免这个问题。但我能想到的唯一一个是使用同步方法,以便在所有请求的响应都返回之前不会执行 removeObjectsInArray (成功或失败) 。但如果我这样做,那就意味着互联网连接必须可以使用更长时间。 sendFeedback的线程所需的时间会更长。即使它将由新创建的NSThread运行,这不会导致应用程序无法响应,无论如何都需要资源。

那么除了上面提到的那个之外还有其他方法吗?欢迎提出任何建议。

谢谢。

2 个答案:

答案 0 :(得分:4)

有几种方法可以解决这类问题。我建议使用调度组来同步您的反馈并使用实例变量来防止执行新的反馈批处理,同时仍在进行中。对于此示例,假设您为类创建名为_feedbackUploadInProgress的实例变量,您可以像这样重写-sendFeedback方法:

- (void)sendFeedback
{
  if( _feedbackUploadInProgress ) return;
  _feedbackUploadInProgress = YES;

  dispatch_group_t group = dispatch_group_create();
  NSMutableArray *sentFeedback = [NSMutableArray array];
  for (NSMutableDictionary *feedback in self.feedbackQueue) {
    // enter the group for each item we're uploading
    dispatch_group_enter(group);
    NSURL *url = [NSURL URLWithString:@"someApiUrl"];
    ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
    [request setPostValue:[feedback objectForKey:@"data"] forKey:@"data"];
    [request setCompletionBlock:^{
      [sentFeedback addObject:feedback];
      // signal the group each time we complete one of the feedback items
      dispatch_group_leave(group);
    }];
    [request startAsynchronous];
  }
  // this next block will execute on the specified queue as soon as all the
  // requests complete
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    [self.feedbackQueue removeObjectsInArray:sentFeedback];
    _feedbackUploadInProgress = NO;
    dispatch_release(group);
  });
}

答案 1 :(得分:1)

一种方法是跟踪请求飞行并在完成后清理队列。跟踪块是一个有点棘手,因为天真的方法将产生一个保留周期。这是做什么的:

- (void)sendFeedback {

    NSMutableArray *sentFeedback = [NSMutableArray array];

    // to keep track of requests
    NSMutableArray *inflightRequests = [NSMutableArray array];

    for (NSMutableDictionary *feedback in self.feedbackQueue){
        NSURL *url = [NSURL URLWithString:@"someApiUrl"];

        ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];

        // save it
        [inflightRequests addObject:request];

        // this is the ugly part. but this way, you can safely refer
        // to the request in it's block without generating a retain cycle
        __unsafe_unretained ASIFormDataRequest *requestCopy = request;

        [request setPostValue:[feedback objectForKey:@"data"] forKey:@"data"];
        [request setCompletionBlock:^{
            [sentFeedback addObject:feedback];

            // this one is done, remove it
            // notice, since we refer to the request array here in the block,
            // it gets retained by the block, so don't worry about it getting released
            [inflightRequests removeObject:requestCopy];

            // are they all done?  if so, cleanup
            if (inflightRequests.count == 0) {
                [self.feedbackQueue removeObjectsInArray:sentFeedback];
            }
        }];
        [request startAsynchronous];
    }
    // no cleanup here.  you're right that it will run too soon here
}