Generics
Um código genérico é um código que trabalha de forma abstrata, ganhando flexibilidade e evitando redundâncias. Boa parte da base da Swift é escrita utilizando generics, e essa é uma das razões da linguagem ser tão poderosa.
Um ótimo exemplo da utilização de generics na Swift é na utilização das Coleções. Já se perguntou como estes tipos aceitam elementos de tantos tipos diferentes? 🤔
// Declaramos um array de elementos do tipo Int,
// ou seja [Int] está implícito.
let idades = [ 10, 20, 10 ]
// [Int] na verdade é apenas uma maneira -- mais bonita -- de representar
// a forma natural: Array<Int>.
// Dessa forma temos explicitamente a representação da _coleção_ (Array)
// e do tipo do elemento (<Int>).
// Então podemos declaramos um array de elementos do tipo String dessa forma:
let nomes: Array<String> = [ "Merola", "Joselito", "Lucas" ]
// Nos dicionários, chave e valor são genéricos,
// neste caso definimos como String e Double
let pesos: Dictionary<String, Double> = [ "Joao": 86.9, "Carla": 54.2 ]
Como Usar?
Para utilizar generics em nosso próprio código é bem simples, precisamos criar a representação de um tipo e quando precisar referenciá-lo, usar essa representação. Vamos imaginar que precisamos implementar uma fila, e por simplicidade vamos usar um Array
para armazenar os elementos.
Ao começarmos a implementar já nos deparamos com um problema:
class Fila {
// Como a Swift nos obriga a colocar o tipo do elemento
// aqui temos que falar o que vai nesse array.
private var itens = [Int]()
}
Se não fosse pelo generics, iríamos ter que escrever praticamente o mesmo código para cada variação da nossa Fila
, e acabaríamos com: FilaInt
, FilaString
, FilaDouble
, etc.
Representação de um Tipo
class Fila<T> { // Aqui fica a representação ou seja, no escopo da classe criamos
// uma forma de simbolizar um tipo que ainda não sabemos qual é.
// Repare que agora o tipo é T, não sabemos qual será até a Fila ser utilizada.
private var itens = [T]()
}
No exemplo anterior, utilizamos T
para representar o tipo genérico do parâmetro. No entanto podemos utilizar uma nomenclatura descritiva com o contexto do que está sendo criado para facilitar o entendimento.
class Fila<Elemento> {
private var itens = [Elemento]()
}
Exemplo
class Fila<Elemento> {
private var itens = [Elemento]()
func enfileira(_ item: Elemento) {
itens.append(item)
}
func desenfileira() -> Elemento {
return itens.remove(at: 0)
}
}
// Usando a Fila:
let senha = Fila<Int>()
senha.enfileira(5)
senha.enfileira(9)
var proximo = senha.desenfileira()
// Com isso podemos rapidamente criar filas de qualquer tipo usando Fila<Tipo> :)
Restringindo tipos
Podemos fazer com que nossos tipos genéricos se restrinjam a herdar de uma classe, protocolo ou um conjunto deles. Com isso, conseguimos dar um pouco mais especificidade para um tipo genérico sem ferir o seu conceito abstrato.
// Criamos uma função que recebe um tipo UIView ou descendente:
func adicionarAncora<V: UIView>(view: V) {
// Adicionar constraints para uma view
}
// Repare que para usar generics em funções, o conceito é o mesmo ;)
let botao = UIButton()
adicionarAncora(view: botao)
let foto = UIImageView()
adicionarAncora(view: foto)
// Como nossa função recebe um tipo genérico restrito a UIView, se tentarmos
// usar algo diferente o compilador já detecta o erro:
let numero = 10.0
adicionarAncora(view: numero) // <- Erro
// Aceita apenas valores de tipos que permitam comparação de igualdade
func verificarIgualdade<Valor: Equatable>(valor1: Valor, valor2: Valor) -> Bool {
return valor1 == valor2
}
verificarIgualdade(valor1: 10, valor2: 10)
verificarIgualdade(valor1: 3.14, valor2: 6.12)
verificarIgualdade(valor1: "alpha", valor2: "omega")
Ainda há muito o que falar sobre Generics, e com certeza voltaremos ao assunto. Aproveite para se aprofundar lendo a documentação oficial.
Até a próxima!
>}