【问题标题】:Method For Ending A Loop With Completion Handler?用完成处理程序结束循环的方法?
【发布时间】:2016-04-13 15:39:53
【问题描述】:

也许我在理解整体前提上有一个潜在的问题,但我正在尝试找出用一组项目做某事并结束测试的最佳方法> 一旦找到某个标准,就尽早。

例如,我有一个名称数组;
var names = ["Bob", "Billy", "Sarah", "Brandon", "Brian", "Rick"]

我想测试数组中的每个名称,看看它是否存在于数据库中。为此,我使用完成处理程序调用另一个函数;

for name in names {

    TestName(name) { response in

     if response {
         // END THE LOOP
     } else {
         // KEEP GOING
     }
 }

我无法弄清楚 // END THE LOOP。就本示例而言,我只关心第一次响应为真时(如果数组中存在比利,我对测试 Sarah、Brandon、Brian 或 Rick 没有进一步的兴趣)。

谢谢!

【问题讨论】:

  • 抱歉我的回答不好。这里的问题是你在一个我没有注意到的街区。 :)
  • 顺便说一下,如果 TestName 的完成处理程序是异步的,那么您确实对您的整体前提有潜在的问题!
  • 仅供参考,如果这是同步的,你不应该使用闭包,如果它是异步的,你不能指望使用 for 循环你想要的行为
  • TestName 是异步方法吗?还是同步的?如果是同步的,则有比闭包模式更好、更简单的模式。如果是异步的(正如人们可能从“完成处理程序”术语中推断出来的那样),那么需要一些非常非常不同的东西(例如,一些复杂的设计来取消其他任务)。

标签: arrays swift loops completionhandler


【解决方案1】:

在开始循环之前,设置一个标志:

var exitEarly = false
for name in names {

每次通过循环测试标志:

for name in names {
    if exitEarly {
        break
    }

在 TestName 响应块中,设置标志:

TestName(name) { response in
    if response {
        exitEarly = true
    } else {
        // KEEP GOING
    }
}

但是请注意,如果 TestName 的块是异步执行的,那将不起作用,因为整个循环在调用任何异步块之前(这是异步的本质)。

【讨论】:

  • 这正是我想要的。它满足了我的需要;我很难从理论上解释如何打破循环,但这很有效。
  • Matt,在上面的第二个代码块中,您不是想说for name in names {if exitEarly {break}} 而不是if name in names...
  • 虽然对明显的用例没有“错误”,但假设闭包将同步执行,这是一个非常糟糕的假设/习惯
  • 如果这个闭包同步运行(这不太可能,因为他称之为“完成处理程序”),如果他只是让TestName 返回一个值(基于闭包返回类型或inout 参数)。或者,也许更好的是,他应该完全抛弃封闭模式。但是,它可能没有实际意义,因为它可能是异步的,在这种情况下,这一切都会崩溃。
  • @Rob 同意。这种模式本身就相当恶劣。
【解决方案2】:

您的情况并不是真正为循环设计的。循环可能在循环内的闭包执行之前完成。

尝试使用带有完成块的递归函数:

func databaseHasAName(names: [String], index: Int, completion: (String?) -> ()) {

    guard index < names.count else{
        completion(nil)
        return
    }

    let name = names[index]
    TestName(name) { response in

        if response {
            completion(name)
        } else {
            databaseHasName(names, index: index + 1, completion: completion)
        }
    }
}

这确保了一次只发生一个调用,而不管响应块的同步性如何

【讨论】:

    【解决方案3】:
    1. 在TestName的闭包参数中添加@noescape属性,表示闭包不会转义调用。

    2. 在 TestName 调用之外使用一个变量,设置为 false,如果要停止,则在循环内将其设置为 true。

    3. TestName 调用后,检查变量是否需要中断。

    1. 更改 TestName 以返回一个值,指示它是否应该继续

    【讨论】:

      【解决方案4】:

      我必须和 PEEJWEEJ 做同样的事情。我的异步任务是网络查询和解析,非常密集,for 循环总是在这些任务完成之前完成,因此无法停止它们。

      所以我将它转换为递归并在我希望它停止时设置一个标志,现在它工作正常。

      // ************************************************************************************************************
      // MARK: Recursive function
      // I changed the loop to be recursive so I could stop when I need to - i.e. when selecting another race
      // I set the stopFetching flag in the seque when changing races and the current set of BG queries stops
      // ************************************************************************************************************
      func getRaceResultsRecursive(race: Race, bibs: [Bib], index: Int, completion: @escaping (Bool?) -> ())
      {
          guard index < bibs.count else{
              completion(nil)
              return
          }
      
          var url: URL
          self.stopFetching = false
      
          let bibID = bibs[index]
              url = self.athleteResultAPI.formatURL(baseURL: (race.baseURL)!,
                                                    rd: (race.rdQueryItem)!,
                                                    race: (race.raceQueryItem)!,
                                                    bibID: "\(bibID.bib!)",
                  detail: (race.detailQueryItem)!,
                  fragment: (race.detailQueryItem)!,
                  webQueryType: (race.webQueryType!))
      
          self.athleteResultAPI.fetchAthleteResults(url: url, webQueryType: race.webQueryType!)
          {
              (athleteReturnCode) in
              switch athleteReturnCode
              {
              case let .success(athleteResult):
                  self.athleteResults.append(athleteResult)       // Add to collection
                  self.delegate?.athleteResultsUpdated()           // update Delegate to notify of change
              case let .failure(error):
                  print(error)
                  break
              }
      
              if self.stopFetching {
                  completion(true)
              }
              else {
                  self.getRaceResultsRecursive(race: race, bibs: bibs, index: index + 1, completion: completion)
              }
          }
      }
      
      // ************************************************************************************************************
      // getRaceResults
      // Get all the bibs to track for this race from the iCloudKit database
      // ************************************************************************************************************
      func getRaceResults(race: Race)
      {
          // get all the races and bibs an put in the Races Store
          raceStore.fetchBibsForRace(race: race, foreignKey: race.recordID)
          {
              (results, error) in
              if let error = error {
                  print("Error in fetchBibsForRace(): \(error)")
                  return
              }
      
              // clear the store since we are on a new race
              self.athleteResults.removeAll()
              self.getRaceResultsRecursive(race: race, bibs: race.bibs, index: 0)
              {
                  (stopFetching) in
                  return
              }
      
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多