避免嵌套的Swift forEach闭包?

9

假设我有一个闭包数组,我想在每个UITouch上运行它们。这是我使用的代码:

touches.filter { touch in
    return touch.phase == .Ended && touch.tapCount == 1
}.forEach { touch in
    actionsOnTap.forEach { action in
        action(touch)
    }
}

我感到困扰的是有嵌套的forEach语句,我猜想有一些干净的方法可以应用于这种情况,但我想不出来。有人能给我一些提示吗?


6
为了将每个触摸动作组合起来,您需要一些嵌套循环。我不认为有内置的库函数可以创建两个列表的“乘积”,但我当然可能是错的。您可以制作一个实用程序函数来实现这个目的,例如在http://stackoverflow.com/questions/30422312/swift-list-product中所示。 - Martin R
1
@MartinR 如果列表是嵌套的,您可以使用 flatMap 来创建它们的乘积。 - Charles A.
3
你有两个独立的列表(touches和actions),而不是一个嵌套列表。 - Martin R
@MartinR 那么它就必须是 zip 函数了。但仍然可能是更糟糕的选择。 - Charles A.
是的,在重新阅读文档后,我删除了我的答案。他已经拥有的似乎已经是最好的选择了。 - Charles A.
显示剩余5条评论
4个回答

11

就我个人而言,我喜欢嵌套。我会写:

for touch in touches {
    if touch.phase == .Ended {
        if touch.tapCount == 1 {
            actionsOnTap.forEach {$0(touch)}
        }
    }
}

对我而言,那是干净且(最重要的是)清晰的。


9
你应该从逻辑中删除 filter 并在第一个循环中使用 guard,以提高效率和简洁性。我也同意@Rob's@matt's 建议,至少在第一个循环中使用传统的 for 循环而不是 forEach
虽然另一种(可能更干净的)选择是通过使用 where 子句将触摸条件逻辑直接整合到 for 循环中,并将你的 forEach 折叠成单行(无论哪个更易读)。
我会这样写:
for touch in touches where touch.phase == .Ended && touch.tapCount == 1 {
    actionsOnTap.forEach{$0(touch)}
}

我忘记在这里使用 where。谢谢。 - Rob Napier
这绝对是最好的解决方案。我不知道我们可以在循环中使用“where”语句,谢谢! - Alexander Woodblock

6

这是一个很好的例子,说明为什么forEach不能普遍(或甚至适当常见)替代for-in。使用传统的for循环可以使代码变得更短(140个字符对比186个字符),更加清晰:

for touch in touches where touch.phase == .Ended && touch.tapCount == 1 {    
    for action in actionsOnTap {
        action(touch)
    }
}

它也不像filter那样创建额外的数组副本。这不是不使用filter的普遍原因。 filter是一个非常强大的工具,应该经常使用,但在这种情况下,使用for更清晰和更有效率。已编辑以使用@originaluser2的建议where而不是guard。这可能是更好的Swift。

1
由于您有两种异构数组类型。另一种避免额外迭代的解决方案是自己过滤要检查的触摸事件。
touches.forEach{
  guard $0.phase == .Ended && $0.tapCount == 1 else { return }

  actions.forEach{ action in
     action($0)
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接