1

根据迭代时从 NSMutableArray 中删除的最佳方法?,我们不能在迭代时从 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 运行,这不会导致应用程序不响应,但无论如何都需要资源。

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

谢谢你。

4

2 回答 2

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);
  });
}
于 2012-04-05T04:37:26.413 回答
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
}
于 2012-04-05T04:54:05.833 回答