Portuguese (Português) translation by David Batista (you can also view the original English article)
Se você trabalhou com blocos em C/Objective-C ou lambdas em Ruby, então você não terá problemas para entender o conceito de closures. Closures são nada mais do que blocos de funcionalidade que você pode passar durante seu código.
Na verdade, nós já trabalhamos com closures nos dois artigos anteriores. É isso mesmo. Funções são closures também. Vamos começar com o básico e inspecionar a anatomia de uma closure.
1. O que é uma closure?
Como eu disse, uma closure é um bloco de funcionalidade que você pode passar durante seu código. Você pode passar uma closure como um argumento de uma função ou você pode armazená-la como uma propriedade de um objeto. Closures tem muitos casos de uso.
O nome closure (fecho) sugere uma das características chave das closures. Uma closure captura as variáveis e constantes do contexto no qual ela está definida. Isto é por vezes referido como closing over (fechando sobre) as variáveis e constantes. Nós vamos olhar para captura de valor mais detalhadamente no final deste artigo.
Flexibilidade
Você já aprendeu que funções podem ser incrivelmente poderosas e flexíveis. Como as funções são closures, closures são igualmente flexíveis. Neste artigo, você vai descobrir quão flexível e poderosa elas são.
Gerenciamento de memória
A linguagem de programação C tem um conceito similar, blocos. As closures em Swift, no entanto, tem alguns benefícios. Uma das vantagens chave das closures em Swift é que o gerenciamento de memória é algo que você, o desenvolvedor, não tem que se preocupar.
Até reter ciclos, os quais não são incomuns em C/Objective-C, são manipulados pelo Swift. Isto irá reduzir vazamentos de memória difíceis de encontrar ou falhas devido a ponteiros inválidos.
2. Sintaxe
A sintaxe básica de uma closure não é difícil e irá lembrá-lo de funções encadeadas e globais, que abordamos anteriormente nesta série. Dê uma olhada no exemplo a seguir.
{(a: Int) -> Int in return a + 1 }
A primeira coisa que você notará é que a closure inteira é cercada por chaves. Parâmetros do closure são cercadas por parênteses, separadas do tipo de retorno pelo símbolo ->
. A closure acima aceita um argumento, a
, do tipo Int
e retornar um Int
. O corpo da closure começa depois da palavra-chave in
.
Closures nomeadas, ou seja, funções encadeadas e globais, parecem um pouco diferente. O exemplo a seguir deve ilustrar as diferenças.
func increment(a: Int) -> Int { return a + 1 }
As diferenças mais importantes são o uso da palavra-chave func
e a posição dos parâmetros e tipo de retorno. Uma closure começa e termina com chaves, envolvendo os parâmetros, tipo de retorno e o corpo da closure. Apesar dessas diferenças, lembre-se que toda função é uma closure. Mas nem toda closure é uma função.
3. Closures como parâmetros.
Closures são poderosas e o exemplo a seguir ilustra como elas podem ser úteis. No exemplo, criamos um array de estados. Chamamos a função map
no array para criar um novo array que contém apenas as duas primeiras letras de cada Estado com uma string em maiúsculas.
var states = ["California", "New York", "Texas", "Alaska"] let abbreviatedStates = states.map({ (state: String) -> String in return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString }) println(abbreviatedStates)
O método ou função map
é comum em muitas linguagens de programação e bibliotecas, como jQuery, PHP e Ruby. No exemplo acima, a função map
é chamada no array de states
, transforma o seu conteúdo e retorna um novo array que contém os valores transformados.
Inferência de tipo
Anteriormente nesta série, aprendemos que o Swift é bastante inteligente. Vamos ver exatamente inteligente quanto. A array de Estados é um array de strings. Como chamamos a função map
no array, o Swift sabe que o argumento state
é do tipo String
. Isto significa que podemos omitir o tipo, conforme mostrado no exemplo abaixo atualizado.
let abbreviations = states.map({ (state) -> String in return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString })
Existem mais algumas coisas que podemos omitir do exemplo acima, originando a seguinte linha de comando.
let abbreviations = states.map({ state in state.substringToIndex(advance(state.startIndex, 2)).uppercaseString })
Deixe-me explicar o que está acontecendo. O compilador pode deduzir que retornaremos uma string da closure que passamos para a função map
, o que significa que não há nenhuma razão para incluí-lo na definição de expressão da closure. Só podemos fazer isso se o corpo da closure inclui uma única instrução. Nesse caso, podemos colocar essa declaração na mesma linha como na definição da closure, conforme mostrado no exemplo acima. Como não há nenhum tipo de retorno na definição e não precede o símbolo ->
no tipo de retorno, podemos omitir os parênteses delimitador dos parâmetros da closure.
Nomes de argumento abreviados
Porém não para por aqui. Nós podemos fazer uso de nomes de argumento abreviados para simplificar ainda mais a expressão da closure acima. Ao usar uma expressão da closure inline, como no exemplo acima, podemos omitir a lista de parâmetros, incluindo a palavra-chave in
que separa os parâmetros do corpo de encerramento.
No corpo da closure, referenciamos o argumento usando o nome do argumento abreviado que o Swift nos fornece. O primeiro argumento é referenciado por $0
, o segundo por $1
, etc.
No exemplo atualizado a seguir, eu omiti a lista de parâmetros e a palavra-chave in
, e troquei o argumento state
no corpo da closure com o nome do argumento abreviado $0
. A instrução resultante é mais concisa, sem comprometer a legibilidade.
let abbreviations = states.map({ $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString })
Trailing closures
A linguagem de programação Swift também define um conceito conhecido como trailing closures. A ideia é simples. Se você passar uma closure como o último argumento de uma função, você pode colocar essa closure fora dos parênteses da chamada de função. O exemplo a seguir demonstra como isso funciona.
let abbreviations = states.map() { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }
Se o único argumento da chamada de função é a closure, é possível omitir os parênteses da chamada de função.
let abbreviations = states.map { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }
Note que isto também funciona para closures que contêm várias instruções. Na verdade, é a principal razão da trailing closure está disponível em Swift. Se uma closure for longa ou complexa, e é o último argumento de uma função, muitas vezes é melhor usar a sintaxe de training closure.
let abbreviations = states.map { (state: String) -> String in let newState = state.substringToIndex(advance(state.startIndex, 2)) return newState.uppercaseString }
4. Capturando valores
Quando usamos closures, você encontra-se frequentemente usando ou manipulando constantes e variáveis do ambiente da closure no corpo da closure. Isso é possível e muitas vezes referido como captura de valor. Isso simplesmente significa que uma closure pode capturar os valores de constantes e variáveis do contexto que é definido. Veja o exemplo a seguir para entender melhor o conceito de captura de valor.
func changeCase(uppercase: Bool, strings: String...) -> [String] { var newStrings = [String]() func changeToUppercase() { for s in strings { newStrings.append(s.uppercaseString) } } func changeToLowerCase() { for s in strings { newStrings.append(s.lowercaseString) } } if uppercase { changeToUppercase() } else { changeToLowerCase() } return newStrings } let uppercaseStates = changeCase(true, "Califorina", "New York") let lowercaseStates = changeCase(false, "Califorina", "New York")
Tenho certeza que você concorda que o exemplo acima é um pouco amplo, mas mostra claramente como a captura de valor funciona em Swift. As funções encadeadas, changeToUppercase
e changeToLowercase
, tem acesso aos argumentos da função externa, changeCase
, bem como a variável newString
declarado na função exterior. Deixe-me explicar o que acontece.
A função changeCase
aceita um boolean como primeiro argumento e o parâmetros variável do tipo String
como segundo parâmetro. O parâmetro de retorno é um array de string composto pelas strings passadas para a função como segundo argumento. No corpo da função, criamos um array mutável de strings, newStrings
, no qual armazenamos e manipulamos as strings.
As funções encadeadas percorrem as string que são passadas para a função changeCase
e alteram o case de cada string. Como você pode ver, elas tem acesso direto as strings passadas para a função changeCase
bem como o array newStrings
, que é declarado no corpo da função changeCase
.
Verificamos o valor de uppercase
, chamando a função apropriada e retornando o array newStrings
. As duas linhas no final do exemplo demonstram como funciona a função changeCase
.
Mesmo que demonstrei a captura de valor com funções, lembre-se que cada função é uma closure. Em outras palavras, as mesmas regras se aplicam para closures sem nome.
Closures
Foi mencionado várias vezes neste artigo, funções são closures. Existem três tipos de closures:
- funções globais
- funções encadeadas
- expressões de closures
Funções globais, tais como a função println
da biblioteca padrão do Swift, sem captura de valores. Funções encadeadas, no entanto, tem acesso e podem capturar os valores das constantes e valores da função em que eles são definidos. O exemplo anterior ilustra este conceito.
Expressões de closures, também conhecido como closures anônimas, podem capturar os valores das constantes e variáveis do contexto que eles são definidos. Isto é muito semelhante a funções encadeadas.
Copiar e referênciar
Uma closure que captura o valor de uma variável é capaz de alterar o valor dessa variável. O Swift é inteligente o bastante para saber se deve copiar ou referenciar os valores das constantes e variáveis capturadas.
Os desenvolvedores que são novos em Swift e tem pouca experiência com outras linguagens de programação terá esse comportamento garantido. No entanto, é uma vantagem importante que o Swift compreenda os valores capturados como estão sendo usados em uma closure e, em conseqüência, pode lidar com gerenciamento de memória para nós.
Aprenda mais em nosso Curso de Programação de Swift
Se você está interessado em elevar sua educação em Swift para o próximo nível, você pode dar uma olhada no nosso curso completo de desenvolvimento em Swift.
Conclusão
Closures é um conceito importante e você o usará frequentemente em Swift. Eles permitem a você escrever um código dinâmico e flexível, que é fácil de escrever e entender. No próximo artigo, iremos explorar orientação a objetos em Swift, começando com objetos, estruturas e classes.
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!