Downcasting in a for loop

for case let rep as NSBitmapImageRep in image.representations {
// `rep` is an NSBitmapImageRep.  Non-bitmap reps are skipped.
}

I sometimes forget that this is possible (and even more often exactly what the damn syntax is – kudos to vacawama in today’s case of this for reminding me with their StackOverflow answer). There are numerous other ways to write the above, but I think it is the most elegant.

Inferior Alternatives

for rep in image.representations {
    if let rep = rep as? NSBitmapImageRep {

    }
}

More indentation, and pointless loop iterations (for non-matching elements) which the compiler won’t necessarily optimise out.

for rep in image.representations where rep is NSBitmapImageRep {
    let rep = rep as! NSBitmapImageRep
    

}

More verbose, and while technically correct it is more fragile as it relies on forced downcasting which is vulnerable to later code changes unwittingly breaking the contract.

for rep in image.representations.compactMap({ $0 as? NSBitmapImageRep }) {

}

More verbose and harder to read, plus it’s not as efficient – it will create a temporary Array containing the result, then iterate over that. Besides the increased memory usage, that wastes CPU time with a bunch of unnecessary memory management.

for rep in image.representations.lazy.compactMap({ $0 as? NSBitmapImageRep }) {

}

Probably as efficient as the ideal approach – the Swift compiler is generally quite good at optimising away the compactMap boilerplate in simple cases like this – but it’s still more verbose and harder to read.

image.representations.lazy.compactMap { $0 as? NSBitmapImageRep }.forEach { rep in

}

Still verbose and hard to read, and probably not as efficient as the ideal approach – now you’ve implicitly split your function into two parts1 – one the parts above & below this loop, and one the closure for the loop body. That means function-call overheads, and makes it a little harder to debug – you can’t as trivially step through the code, and it pollutes the symbol namespace (closures are anonymous so you end up with ugly, inscrutable autogenerated names).

Broken “Alternatives”

for rep in image.representations as! [NSBitmapImageRep] {

}

Blindly assumes every element in the array is the desired type, which might not be the case, in which case it will probably crash (or worse, not crash but have undefined and almost certainly bad behaviour). Also, the syntax isn’t fully generic – the above example only works if the thing being iterated over is an Array (or implicitly convertible to one, like NSArray in these examples).

And there are a number of other ways to write essentially the same thing, of making dangerous presumptions about the contents of the sequence / collection.

  1. As far as I can tell there’s no reason, in principle, that the compiler couldn’t optimise away the closure and return the function to a single procedure. But I’ve never seen it make that optimisation in practice. ↩︎

Leave a Comment