Casting de tipo em loop for-in

116

Eu tenho este loop for-in:

for button in view.subviews {
}

Agora quero que o botão seja convertido em uma classe personalizada para que possa usar suas propriedades.

Eu tentei isso: for button in view.subviews as AClass

Mas não funciona e me dá um erro:'AClass' does not conform to protocol 'SequenceType'

E eu tentei isso: for button:AClass in view.subviews

Mas também não funciona.

Arbitur
fonte
Que talfor button in view.subviews as [AClass]
vacawama
2
haha Eu não pensei nisso, claro que é melhor lançar o array em um array de AClass, obrigado cara. Você pode responder sobre isso para que eu possa dar-lhe algumas representações :)
Arbitur

Respostas:

173

Para Swift 2 e posterior:

O Swift 2 adiciona padrões de caso a loops for , o que torna ainda mais fácil e seguro digitar cast em um loop for :

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

Por que isso é melhor do que o que você poderia fazer no Swift 1.2 e anteriores? Porque os padrões de caso permitem que você escolha seu tipo específico da coleção. Ele corresponde apenas ao tipo que você está procurando, portanto, se o seu array contiver uma mistura, você poderá operar apenas em um tipo específico.

Por exemplo:

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

Resultado:

Hello
World!

Para Swift 1.2 :

Nesse caso, você está lançando view.subviewse não button, então precisa fazer o downcast para a matriz do tipo que deseja:

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

Nota: Se o tipo de array subjacente não for [AClass], isso irá travar. Isso é o que o !on as!está dizendo a você. Se você não tiver certeza sobre o tipo, pode usar um elenco condicional as?junto com a vinculação opcional 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
    }
}

Para Swift 1.1 e anterior:

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

Nota: Isso também travará se as subvisualizações não forem todas do tipo AClass. O método seguro listado acima também funciona com versões anteriores do Swift.

Vacawama
fonte
Apenas uma observação para outras pessoas como eu, que não entenderam a princípio - o nome da classe deve ficar [ ]entre colchetes exatamente como neste exemplo. Talvez seja só eu, mas pensei que fosse apenas uma escolha de formatação no exemplo de código no início :) Presumo que seja porque estamos lançando para um array.
@kmcgrady [Class]Parece muito melhor do que Array<Class>.
Arbitur
Portanto, por curiosidade, há uma maneira de fazer várias instruções de caso dentro do loop for? EX .: se eu quiser fazer uma coisa com os botões e outra com os campos de texto?
RonLugge
125

Esta opção é mais segura:

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

ou maneira rápida:

view.subviews
  .compactMap { $0 as AClass }
  .forEach { .... }
ober
fonte
1
Esta parece ser a resposta mais agradável, pois não há elenco de força.
Patrick
1
Esta deve ser a resposta aceita. É o melhor e o correto!
Thomás Calmon de
Resposta correta. Curto e simples.
PashaN
4

Você também pode usar uma wherecláusula

for button in view.subviews where button is UIButton {
    ...
}
edelaney05
fonte
12
A cláusula where é uma guarda booleana, mas não lança o tipo de buttondentro do corpo do loop, que é o objetivo das outras soluções. Portanto, isso só executará o corpo do loop em elementos view.subviewsque são UIButtons, mas não ajuda, por exemplo, na chamada de AClassmétodos específicos button.
John Whitley
1
@JohnWhitley você está correto que whereapenas guarda e não lança.
edelaney05
wherepode ser mais elegante e legível para casos (trocadilhos não intencionais) em que você precisa apenas de um guarda booleano.
STO de
3

As respostas fornecidas estão corretas, eu só queria adicionar isso como um acréscimo.

Ao usar um loop for com conversão de força, o código irá travar (como já mencionado por outros).

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

Mas em vez de usar uma cláusula if,

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

outra maneira é usar um loop 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
    // ...
}

O que parece ser mais conveniente se alguns elementos (mas não todos) do array forem do tipo AClass.

Embora, por enquanto (a partir do Swift 5), eu prefira o loop for-case:

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

for case let subview as AClass in view.subviews {
    // ...
}
user0800
fonte
Só uma dúvida aqui ... a função enumerada rápida realiza iterações / loop aqui ... só de pensar que refatorar para perder desempenho não será ótimo ... obrigado
Amber K
2

A resposta fornecida por vacawama estava correta no Swift 1.0. E não funciona mais com o Swift 2.0.

Se você tentar, obterá um erro semelhante a:

'[AnyObject]' não é conversível para '[AClass]';

No Swift 2.0, você precisa escrever como:

for button in view.subviews as! [AClass]
{
}
Midhun MP
fonte
0

Você pode realizar o casting e estar seguro ao mesmo tempo com isto:

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

}
Nandodelauni
fonte