iOS:在while循环中带有块回调的异步方法

Posted

技术标签:

【中文标题】iOS:在while循环中带有块回调的异步方法【英文标题】:iOS: Asynchronous method with block callback in a while loop 【发布时间】:2015-02-19 17:46:12 【问题描述】:

我有以下要求:

鉴于分层树状结构,我正在执行breadth-first-search 遍历整个数据集。数据由 API 提供,方法如下:(使用 AFNetworking 向服务器发出请求,将结果保存到 Core Data 并在成功时使用存储的条目回调完成块)

-(void) getChildrenForNodeId:(NSNumber*)nodeId completion:(void (^)(NSArray *nodes))completionBlock;

控制器执行获取数据的方法:

 -(void)getAllNodesWithCompletion:(void (^)(NSArray *nodes))completionBlock

     NSNumber *rootId = ...

     [MyNetworkManager getChildrenForNodeId:rootId completion:^(NSArray *nodes)

        for(Node *node in nodes)

             [self iterateOverNode:node.nodeId];

        

       //execute completionBlock with nodes fetched from database that contain all their children until the very last leaf

     ];

  

问题来了:

-(void)iterateOverNode:(NSNumber*)nodeId 

    NSMutableArray *elements = [NSMutableArray array];

    [elements addObject:nodeId];

    while ([elements count]) 

        NSNumber *current = [elements objectAtIndex:0];

        [MyNetworkManager getChildrenForNodeWithId:current completion:^(NSArray *nodes) 

              /**
                In order to continue with the loop the elements array must be updated. This can only happen once we have retrieved the children of the current node. 
However since this is in a loop, all of these requests will be sent off at the same time, thus unable to properly keep looping. 
          */
          for(Node *node in nodes)
              [elements addObject:node.nodeId];
          

          [elements removeObjectAtIndex:0];

        ];

    


基本上我需要回调的结果来控制while循环的流程,但我不知道如何实现它。我的理解是,从while-loop 中对getChildrenForNodeWithId:completion: 的请求应该以串行顺序在一个新线程中发生,以便在第一个线程完成后开始另一个线程。我不确定如何使用 NSOperation 或 GCD 来实现这一点。任何帮助将不胜感激。

【问题讨论】:

那么您是在问如何下载所有节点并将它们保存到核心数据中,因为每个节点都可能包含其他节点? 没错。我理解这个问题,我不知道如何使用异步 Web 服务请求来解决这个问题。 MyNetworkManager 的实现是什么? AFHTTPRequestOperationManager 的子类,它调用给定信息的 API。检索数据时,会将其保存到核心数据中。保存完成后,执行完成块。 我的解决方案有帮助吗? 【参考方案1】:

你需要的是一些递归。这个问题很棘手,因为我们还需要一种方法来跟踪和检测我们探索每个分支到叶节点的点。

我不是树搜索算法方面的专家,所以有些人可能会在这里改进我​​的答案。通过使用根节点 ID 调用它来启动它。 self.trackingArray 是带有 __block 限定符的 NSMutableArray 属性。每次我们开始请求一个节点时,我们将它的nodeId添加到这个数组中,当它返回时,我们删除它的nodeId,并添加它的孩子的nodeIds。然后我们可以知道,当跟踪数组的计数达到0时,每个请求都返回了,并且没有将子ID添加到数组中。一旦你检测到我们已经完成,你可以调用一个保存的块或委托方法。

此解决方案不包括任何错误处理。如果任何请求失败,则不会重试,所有子节点都将被忽略。

- (void)getNodesRecursively:(NSNumber *)nodeId 

    // Only do this once
    if (self.trackingArray == nil) 
        self.trackingArray = [NSMutableArray new];
        [self.trackingArray addObject:nodeId];
    
    __weak typeof(self) weakSelf = self;

    [MyNetworkManager getChildrenForNodeWithId:nodeId completion:^(NSArray *nodes) 

        [self.trackingArray removeObject:nodeId];

        for (Node *node in nodes) 

            [weakSelf.trackingArray addObject:node.nodeId];
            [weakSelf getNodesRecursively:node.nodeId];
        

        if (weakSelf.trackingArray.count == 0) 
            // We are done.
            // Reset the state of the trackingArray
            self.trackingArray = nil;

            // call a method here to notify who ever needs to know.
            [weakSelf allNodesComplete];
        

    ];

你的其他方法看起来像这样

-(void)getAllNodesWithCompletion:(void (^)(NSArray *nodes))completionBlock

     // Save the block to a property 
     self.completion = completionBlock;

     // Call -getNodesRecursively with root id
     [self getNodesRecursively:rootNodeId];

那么你可以有第二种方法

- (void)allNodesComplete 

      // Call saved block
      // Completion should load nodes from core data as needed
      self.completion();

我还没有测试过代码,但是这种方法看起来合理吗?我假设我们不需要捕获此处的节点,因为它们可以根据需要从核心数据中加载。

【讨论】:

以上是关于iOS:在while循环中带有块回调的异步方法的主要内容,如果未能解决你的问题,请参考以下文章

小痴Nodejs升级路---事件循环eventloop

当我进行异步调用时,是不是有必要在 iOS 和 Android 的 while 循环中使用 Thread.Sleep(1)?

道路交通库 - MoveTo 块 - while 循环?

node.js:while 循环回调未按预期工作

JavaScript:while循环中的异步方法

循环javascript时的回调方法