Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you continue a loop if optional downcasting fails in Swift?

It's a very common idiom to continue a loop if some condition fails on an element.

Say we want to do something to all subviews of a certain type (and, for some reason, don't want to duck type things). Ideally, we would write:

for view in self.subviews as [NSView] { // cast required in beta 6
    if (let specificView = view as? SpecificView) == nil { // <- Error here
        continue
    }

    // Do things at a sensible indentation level
}

The above code fails with 'Pattern variable binding cannot appear in an expression', as in this question.

However, this seems like such a common pattern that there has to be a way to do it in Swift. Am I missing something?


EDIT: Now that I think about it, this appears to fall afoul of the scoping rules for if let statements, which only scope the variable to the inner block.

With that in mind, I'd like to broaden the question a little: how do people apply this pattern generally in Swift?

like image 323
sapi Avatar asked Aug 25 '14 12:08

sapi


1 Answers

This is a somewhat common pattern, but it is not a good pattern. I've seen it inject bugs into ObjC projects. It assumes too much about the view hierarchy, and winds up being fragile when that changes (such as when someone injects an extra view you weren't expecting in order to manage rotations; true story). The better pattern is to maintain a property that points to your SpecificView (or views) that you want to track. Downcasting in general is something to be avoided, not optimized.

That said, it is not a terrible pattern, and sometimes it is a very useful pattern. So how might you handle it?

let specifics = self.subviews
  .filter { $0 is SpecificView }
  .map { $0 as SpecificView }

for view in specifics { ... }

That's kind of a common pattern, so maybe we can genericize it?

extension Array {
  func filterByClass<T>(c: T.Type) -> [T] {
    return self.filter { $0 is T }.map { $0 as T }
  }
}

for view in self.subviews.filterByClass(SpecificView) { ... }

That said, I think this approach should be avoided wherever possible rather than excessively simplified.

like image 77
Rob Napier Avatar answered Oct 15 '22 04:10

Rob Napier