首页 > 解决方案 > sequence() 上的 compactMap 不懒惰?

问题描述

每隔一段时间,我就必须沿着响应者链向上走,才能到达一个已知类的实例。(出于问题的目的,请接受这一点。)我一直在使用 while 循环来执行此操作,但我想到使用它会更酷sequence(),它可以像这样巧妙地表达响应者链本身:

let chain = sequence(first: someView as UIResponder) {$0.next}

这太棒了,因为到目前为止我们实际上还没有进行任何步行。该序列是惰性的,并且在我们开始请求元素之前不会执行匿名函数。为了证明这一点,让我用 print 语句检测该代码:

let chain = sequence(first: someView as UIResponder) {r in print(r); return r.next}

好的,假设我正在寻找链中的第一个 ViewController 实例。我可以像这样找到它:

if let vc = (chain.first {$0 is ViewController}) as? ViewController {
    print(vc)
}

打印输出显示惰性得到了维持:我们沿着响应者链向上走,直到到达 ViewController 并停止。完美的!在花括号内,vc类型为 ViewController,我们开始比赛了。

然而,它不会逃过你的注意,那是丑陋的。我正在测试和铸造。有没有一种方法可以让我在不测试的情况下直接投射并最终得到一个 ViewController?

这很优雅,而且效果很好:

for case let vc as ViewController in chain {
    print(vc)
    break
}

这很可爱,并且保持了懒惰——但我必须记得break在最后说,哪种会毁了一切。

好的,所以当我想到这一点时,我真的充满希望:

if let vc = (chain.compactMap{ $0 as? ViewController }.first) {
    print(vc)
}

从某种意义上说,它可以编译并获得正确的答案并且看起来不错,但是我已经失去了惰性。整个chain正在被遍历。会compactMap失去懒惰吗?有没有办法把它找回来?(或者有什么其他优雅的方式让我完全逃脱了?)

标签: swiftlazy-sequences

解决方案


问题本身不是compactMap。有两个问题:

  1. 如果您希望序列compactMap延迟调用,则需要使用lazy.

  2. 看起来这first正在阻止懒惰的行为。但是,如果您使用first(where:),您确实喜欢这种懒惰的行为。

因此,虽然有些不雅,但以下实现了您正在寻找的内容:

if let vc = (chain.lazy.compactMap { $0 as? ViewController }.first { _ in true } ) {
    ...
} 

或者,正如您所说,您可以在以下位置实现first(或lazyFirstSequence

extension Sequence {
    var first: Element? {
        return first { _ in true }
    }
}

然后这个更简化的演绎现在仍然是懒惰的:

if let vc = chain.lazy.compactMap({ $0 as? ViewController }).first {
    ...
} 

推荐阅读