Приведение типов в цикле for-in


116

У меня есть этот цикл:

for button in view.subviews {
}

Теперь я хочу, чтобы кнопка была преобразована в специальный класс, чтобы я мог использовать ее свойства.

Я пробовал это: for button in view.subviews as AClass

Но это не работает и выдает ошибку:'AClass' does not conform to protocol 'SequenceType'

И я попробовал это: for button:AClass in view.subviews

Но и это не работает.


Как насчетfor button in view.subviews as [AClass]
vacawama

2
ха-ха, я не подумал об этом, конечно, лучше преобразовать массив в массив AClass, спасибо, чувак. Вы можете ответить на этот вопрос, чтобы я мог дать вам репутацию :)
Арбитур

Ответы:


173

Для Swift 2 и новее:

Swift 2 добавляет шаблоны case в циклы for , что делает еще проще и безопаснее вводить приведение в цикле for :

for case let button as AClass in view.subviews {
    // do something with button
}

Почему это лучше, чем то, что вы могли сделать в Swift 1.2 и ранее? Потому что шаблоны кейсов позволяют выбрать из коллекции именно ваш тип. Он соответствует только тому типу, который вы ищете, поэтому, если ваш массив содержит смесь, вы можете работать только с определенным типом.

Например:

let array: [Any] = [1, 1.2, "Hello", true, [1, 2, 3], "World!"]
for case let str as String in array {
    print(str)
}

Вывод:

Hello
World!

Для Swift 1.2 :

В этом случае вы выполняете преобразование, view.subviewsа не buttonпреобразование, поэтому вам нужно преобразовать его в массив нужного вам типа:

for button in view.subviews as! [AClass] {
    // do something with button
}

Примечание. Если базовый тип массива не является типом [AClass], произойдет сбой. То есть то , что !на as!это говорит вам. Если вы не уверены в типе, вы можете использовать условное приведение as?вместе с необязательной привязкой if let:

if let subviews = view.subviews as? [AClass] {
    // If we get here, then subviews is of type [AClass]
    for button in subviews {
        // do something with button
    }
}

Для Swift 1.1 и ранее:

for button in view.subviews as [AClass] {
    // do something with button
}

Примечание. Это также приведет к сбою, если все подпредставления не относятся к типу AClass. Указанный выше безопасный метод также работает с более ранними версиями Swift.


Просто замечание для других, таких как я, которые сначала не поняли - имя класса должно быть заключено в [ ]скобки, как в этом примере. Может быть, это только я, но сначала я подумал, что это просто выбор форматирования в примере кода :) Я предполагаю, что это потому, что мы выполняем преобразование в массив.

@kmcgrady Просто [Class]выглядит намного лучше, чем Array<Class>.
Arbitur

Итак, из любопытства, есть ли способ сделать несколько операторов case внутри цикла for? Например, если я хочу сделать одно с кнопками, другое с текстовыми полями?
RonLugge

125

Этот вариант более безопасен:

for case let button as AClass in view.subviews {
}

или быстрый способ:

view.subviews
  .compactMap { $0 as AClass }
  .forEach { .... }

1
Это кажется более приятным ответом, поскольку здесь нет силы.
Патрик

1
Это должен быть принятый ответ. Самый лучший и правильный!
Thomás Calmon

Правильный ответ. Коротко и просто.
PashaN

4

Вы также можете использовать whereпредложение

for button in view.subviews where button is UIButton {
    ...
}

12
Предложение where является логической защитой, но не приводит к типу buttonвнутри тела цикла, что является целью других решений. Таким образом, это будет запускать тело цикла только для элементов, у view.subviewsкоторых есть UIButtons, но не помогает, например, при вызове AClassспецифичных для него методов button.
Джон Уитли

1
@JohnWhitley, ты прав, что whereтолько охраняет и не кастует .
edelaney05

whereможет быть более элегантным и удобочитаемым для случаев (случайный каламбур), когда вам нужна только логическая защита.
STO

3

Приведенные ответы верны, я просто хотел добавить это в качестве дополнения.

При использовании цикла for с принудительным приведением код выйдет из строя (как уже упоминалось другими).

for button in view.subviews as! [AClass] {
    // do something with button
}

Но вместо использования предложения if

if let subviews = view.subviews as? [AClass] {
    // If we get here, then subviews is of type [AClass]
    ...
}

другой способ - использовать цикл while:

/* If you need the index: */
var iterator = view.subviews.enumerated().makeIterator()
while let (index, subview) = iterator.next() as? (Int, AClass) {
    // Use the subview
    // ...
}

/* If you don't need the index: */
var iterator = view.subviews.enumerated().makeIterator()
while let subview = iterator.next().element as? AClass {
    // Use the subview
    // ...
}

Что кажется более удобным, если некоторые элементы (но не все) массива могут иметь тип AClass.

Хотя пока (начиная со Swift 5), я бы выбрал цикл for-case:

for case let (index, subview as AClass) in view.subviews.enumerated() {
    // ...
}

for case let subview as AClass in view.subviews {
    // ...
}

Только одно сомнение здесь ... выполняет ли быстрая перечислимая функция итерации / цикл ... просто подумать, что рефакторинг для потери производительности не будет отличным ... спасибо
Amber K

2

Ответ Vacawama был правильным в Swift 1.0. И больше не работает со Swift 2.0.

Если вы попытаетесь, вы получите ошибку, похожую на:

«[AnyObject]» нельзя преобразовать в «[AClass]»;

В Swift 2.0 вам нужно написать так:

for button in view.subviews as! [AClass]
{
}

0

Вы можете выполнить кастинг и при этом быть в безопасности с этим:

for button in view.subviews.compactMap({ $0 as? AClass }) {

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.