问题
Every once in a while I have to walk up the responder chain to reach an instance of a known class. (Just accept this for purposes of the question.) I've been doing this with a while loop, but it occurred to me that it would be cooler to use sequence()
, which can express the responder chain itself neatly like this:
let chain = sequence(first: someView as UIResponder) {$0.next}
That's brilliant because so far we haven't actually done any walking; the sequence is lazy and the anonymous function won't be executed until we start asking for elements. To prove that, let me instrument that code with a print statement:
let chain = sequence(first: someView as UIResponder) {r in print(r); return r.next}
Okay, so let's say I'm looking for the first ViewController instance in the chain. I can find it like this:
if let vc = (chain.first {$0 is ViewController}) as? ViewController {
print(vc)
}
The printout shows that laziness is maintained: we walked up the responder chain until we got to the ViewController and stopped. Perfect! Inside the curly braces, vc
is typed as ViewController and we're off to the races.
It will not have escaped your attention, however, that that's ugly. I'm both testing and casting. Is there a way I can just cast without testing and still end up with a ViewController?
This is elegant and it works fine:
for case let vc as ViewController in chain {
print(vc)
break
}
That's lovely and laziness is maintained — but I have to remember to say break
at the end, which sort of ruins everything.
Okay, so I was really hopeful when I thought of this:
if let vc = (chain.compactMap{ $0 as? ViewController }.first) {
print(vc)
}
It works in the sense that it compiles and gets the right answer and looks nice, but I've lost laziness. The entire chain
is being traversed. Does compactMap
lose laziness? Is there a way to get it back? (Or is there some other elegant way that has completely escaped me?)
回答1:
The issue is not compactMap
, per se. There are two issues:
If you want the sequence to call
compactMap
lazily, you need to use lazy.It would appear that
first
is preventing the lazy behavior. If you usefirst(where:)
, though, you do enjoy the lazy behavior.
Thus, while somewhat inelegant, the following achieves what you’re looking for:
if let vc = (chain.lazy.compactMap { $0 as? ViewController }.first { _ in true } ) {
...
}
Or, as you say, you can implement first
(or lazyFirst
) on Sequence
:
extension Sequence {
var first: Element? {
return first { _ in true }
}
}
And then this more simplified rendition now is still lazy:
if let vc = chain.lazy.compactMap({ $0 as? ViewController }).first {
...
}
来源:https://stackoverflow.com/questions/56782134/compactmap-on-sequence-not-lazy