Можете ли вы продолжить цикл, если необязательное понижение приведения не удается в Swift?

Очень распространена идиома продолжения цикла, если какое-то условие не выполняется для элемента.

Скажем, мы хотим что-то сделать со всеми подпредставлениями определенного типа (и по какой-то причине не хотим уклоняться от типов). В идеале мы бы написали:

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
}

Приведенный выше код завершается с ошибкой «Привязка переменной шаблона не может отображаться в выражении», как в этот вопрос.

Однако это кажется настолько распространенным шаблоном, что должен быть способ сделать это в Swift. Я что-то упускаю?


РЕДАКТИРОВАТЬ: Теперь, когда я думаю об этом, это, кажется, противоречит правилам области видимости для операторов if let, которые ограничивают переменную только внутренним блоком.

Имея это в виду, я хотел бы немного расширить вопрос: как люди применяют этот шаблон обычно в Swift?


person sapi    schedule 25.08.2014    source источник
comment
Что произойдет, если вы удалите as [NSView]? Или попробуйте as? [NSView]   -  person idmean    schedule 25.08.2014
comment
@wumm - эта строка в порядке, ошибка на следующей. Я отредактирую вопрос. (Приведение требуется, потому что self.subviews по какой-то причине является [AnyObject?]...)   -  person sapi    schedule 25.08.2014
comment
Ах. Почему вы сравниваете с nil? Не надо этого делать, уберите это.   -  person idmean    schedule 25.08.2014
comment
@wumm - это так. Что я пытаюсь сделать, так это иметь однострочный оператор if, который будет выполняться, если приведение вниз failed, чтобы у меня не было отступа, устремляющегося вправо.   -  person sapi    schedule 25.08.2014
comment
Итак, вы пытаетесь сделать что-то подобное, как в этом вопросе stackoverflow.com/questions/25484718/?   -  person Martin R    schedule 25.08.2014
comment
@MartinR - точно, хорошая находка. Жаль, что, кажется, нет аккуратного решения :(   -  person sapi    schedule 25.08.2014


Ответы (1)


Это несколько распространенный шаблон, но это не хороший шаблон. Я видел, как он вносил ошибки в проекты ObjC. Он слишком много предполагает об иерархии представлений и оказывается хрупким, когда она изменяется (например, когда кто-то вводит дополнительное представление, которого вы не ожидали, чтобы управлять ротацией; реальная история). Лучший шаблон — поддерживать свойство, указывающее на ваши SpecificView (или представления), которые вы хотите отслеживать. Даункастинга вообще следует избегать, а не оптимизировать.

Тем не менее, это не ужасный паттерн, а иногда очень полезный паттерн. Итак, как вы можете справиться с этим?

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

for view in specifics { ... }

Это своего рода общий шаблон, так что, может быть, мы можем его обобщить?

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) { ... }

Тем не менее, я думаю, что этого подхода следует избегать, где это возможно, а не чрезмерно упрощать.

person Rob Napier    schedule 25.08.2014
comment
В этом конкретном примере я согласен, что это немного глупо. Это просто казалось простым для понимания случаем, когда продолжение после неудачного понижения может иметь какой-то смысл. Тем не менее, блочный шаблон должен достаточно хорошо обобщать за счет некоторой ясности (ну, за счет нескольких строк). - person sapi; 25.08.2014
comment
О, и мне любопытно кое-что с вашим общим методом: в чем разница между словами c: T.Type и c: T? Спасибо! - person sapi; 25.08.2014
comment
c: T будет параметром типа T. c: T.Type — это параметр, который задает тип T. С T.Type вы можете передать класс SpecificView в качестве параметра. - person Rob Napier; 25.08.2014