Swift Tricks: Searching for Objects by Type

I was working on an app a few days ago, and managed to nerd snipe myself within a single line of Swift code. I thought it’d make a good short writeup.

An example view hierarchy.

But first, some background: in the app, we had a very basic tree structure of objects. Each one had a parent and some children, and each was potentially of a different concrete type. For the sake of discussion, think of a UIView hierarchy: the parent is the superview, the children are subviews, and each view might be any arbitrary subclass of UIView.

My goal in this instance was to find the first child of a certain parent of a particular type, and get it returned to me as that type. (Let’s call the type SpecialView.) My first attempt looked something like this:

var specialView: SpecialView? {
    return view.subviews.first(where: { $0 is SpecialView }) as! SpecialView
}

This rubbed me the wrong way, specifically because of the is SpecialView/as! SpecialView combination. It seemed like I should be able to do the type check once and get the object back as that type, maybe using as?.

Thankfully, there’s a Swift function for that: flatMap. It’s very similar to map, in that it applies a closure to every element in a collection, but differs in that it drops nils out of the result. This led me to my second revision:

var specialView: SpecialView? {
    return view.subviews.flatMap({ $0 as? SpecialView }).first
}

The nice thing about this approach is that the collection returned from flatMap is of type [SpecialView], so the trailing call to first provides a view of the type I want.

However, there’s a cost here: where previously we’d stop enumerating subviews once we found the first SpecialView, with flatMap we run all the way through the array before getting the first result. If subviews is an especially large array, this could become a performance hotspot very quickly.

Once again, the Swift standard library comes to the rescue, this time with lazy. This handy property returns a special collection that reimplements flatMap (along with several other related functions) so that they don’t perform all their work right away. Instead, this lazy collection can store the closure we pass to it, evaluating it only when a later call needs a result – like first.

This brought me to the implementation I settled on:

var specialView: SpecialView? {
    return view.subviews.lazy.flatMap({ $0 as? SpecialView }).first
}

It’s very similar to the previous iteration, but the strategic application of lazy saves work in the case that subviews is very large (and the SpecialView isn’t at the end of the array). It also reads fairly concisely, even if you’re unsure what lazy does – and the phrasing of “lazy flat map” lends itself well to searching documentation or other resources.

I’m always interested in other opinions about these sorts of problems! Let me know on Twitter how you’d write this expression, or about other neat applications of lazy.