Extensões

TL;DR: Extensões podem ser usadas para criar novas funcionalidades em tipos existentes: adicionar métodos, propriedades computadas, inicializadores, e assim por diante.  Isso pode ser muito conveniente quando precisamos dessas funcionalidades em tipos que não foram criados por nós, como String, Array, UIColor, UIView

O Swift oferece um recurso único para adicionar funcionalidades a tipos (classes, structs, enums…) já existentes: trata-se das extensões.  A primeira pergunta, claro, é: Por que isso é interessante?

Para tipos que foram criados por nós mesmos, as extensões não parecem muito interessantes.  Afinal, se precisarmos de novos métodos ou inicializadores, basta modificar o nosso próprio código-fonte.  Por outro lado, se o tipo já chegou pronto — um dos tipos básicos como String ou Array, ou classes fornecidas por uma biblioteca de terceiros —, as possibilidades são bastante limitadas, se tanto.  Fica aquele sentimento de “se eu pudesse ensinar alguns truques para essas classes, programar seria muito mais fácil…”

Vou começar este artigo com um exemplo prático, que pode ser baixado neste link.  São dois arquivos: um que define uma classe  Movie, que representa um filme; e outro que inicializa uma array com dezenas de filmes populares.  O meu objetivo é a gente se concentrar na array de filmes, acrescentando funcionalidades que podem ser interessantes: listar os filmes favoritos, descobrir o filme mais antigo ou o mais recente, filtrar filmes por gênero, e assim por diante.

Pesquisando informações em arrays de filmes

Em outras linguagens de programação, como podemos resolver implementar essas funcionalidades?  A solução mais simples é criar funções — aqui estão três:

1
2
3
4
5
6
7
8
9
func getFavouriteMovies(in movies: [Movie]) -> [Movie] {
    var result = [Movie]()
    for movie in movies {
        if movie.isFavourite {
            result.append(movie)
        }
    }
    return result
}
1
2
3
4
5
6
7
8
9
func filterMoviesByGenre(_ movies: [Movie], genre: Movie.Genre) -> [Movie] {
    var result = [Movie]()
    for movie in movies {
        if movie.genres != nil && movie.genres!.contains(genre) {
            result.append(movie)
        }
    }
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
func getOldestMovie(in movies: [Movie]) -> Movie? {
    var result: Movie? = nil
    for movie in movies {
        if movie.year != nil {
            if result == nil {
                result = movie
            } else if movie.year! < result!.year! {
                result = movie
            }
        }
    }
    return result
}

Com isso, podemos buscar informações a partir de uma lista de filmes:

1
2
3
4
let favouriteMovies = getFavouriteMovies(in: topMovies)
let actionMovies = filterMoviesByGenre(topMovies, genre: .action)
let oldestMovie = getOldestMovie(in: topMovies)
let oldestActionMovie = getOldestMovie(in: actionMovies)

Ensinando truques para arrays

Até aqui, nada de novo: funções são nossas velhas conhecidas. Em um mundo ideal, seria muito mais elegante que essas funções bem que poderiam ser propriedades e métodos da própria array, não é verdade? Pois é justamente esse mundo ideal que vamos construir!

Ao invés de criar essas rotinas como funções globais, a ideia é estender a classe Array. Entretanto, métodos e propriedades como “filtre os meus filmes favoritos” ou “retorne o filme mais antigo” só fazem sentido para uma array de filmes, não para qualquer array. Para isso, vamos declarar a extensão de Array associada à classe Movie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
extension Array where Element == Movie {
    var favourites: [Movie] {
        var result = [Movie]()
        for movie in self {
            if movie.isFavourite {
                result.append(movie)
            }
        }
        return result
    }

    func filter(byGenre genre: Movie.Genre) -> [Movie] {
        var result = [Movie]()
        for movie in self {
            if movie.genres != nil && movie.genres!.contains(genre) {
                result.append(movie)
            }
        }
        return result
    }

    var oldest: Movie? {
        var result: Movie? = nil
        for movie in self {
            if movie.year != nil {
                if result == nil {
                    result = movie
                } else if movie.year! < result!.year! {
                    result = movie
                }
            }
        }
        return result
    }

}

Percebeu a diferença primordial? Ao invés de operar sobre um argumento movies, os métodos e propriedades usam o próprio self como fonte de dados. O melhor de tudo é que agora podemos usar essas novas funcionalidades como parte de qualquer array de filmes:

1
2
3
4
let favouriteMovies = topMovies.favourites
let actionMovies = topMovies.filter(byGenre: .action)
let oldestMovie = topMovies.oldest
let oldestActionMovie = actionMovies.oldest

Bem mais elegante, não é verdade?

Por que é importante a cláusula “where Element == Movie”?

(Aliás, isso não é uma opção: Se você tirar, não vai funcionar.)

A questão é que essas extensões que criamos só fazem sentido se os elementos da array forem do tipo Movie. Sem isso, propriedades como isFavourite, genres ou year simplesmente não existem — imagine o que o Swift faria se tentássemos usar umaArrayDeStrings.filter(byGenre: .action)?!?

Portanto, a cláusula where Element == Movie é essencial para que o Swift entenda quais são as arrays que ganham essa extensão. Melhor ainda: o editor do Xcode é suficientemente inteligente para realizar o “auto-complete” apenas quando estamos operando com arrays de filmes!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.