Closures

Closures são blocos de código independentes que podem ser passados e usados no seu código, no Swift são similares aos Blocks nas linguagens C e Objective-C e a lambdas em outras linguagens de programação.

Elas podem capturar e armazenar referências para qualquer constante ou variável do contexto em que estão definidas. Este processo é conhecido como closing over (fechando) essas constantes e variáveis. O Swift cuida de todo gerenciamento de memória do processo de captura pra você.

Nota

Não se preocupe se você não está familiarizado com o conceito de captura, pois isso é explicado com detalhes na seção Capturando Valores.

Funções globais e aninhadas, introduzidas na seção Funções, são na verdade um tipo especial de closures. Elaspodem ter uma dessas três formas:

  • Funções globais são closures que tem um nome e não capturam qualquer valor.
  • Funções aninhadas são closures que tem um nome e podem capturar valores da função que a engloba.
  • Expressões closures não possuem nome e são escritas com uma sintaxe mais leve e moderna, o que torna possível a captura de valores do seu contexto.

As closures no Swift possuem um estilo limpo e claro, com otimizações que incentivam uma sintaxe enxuta e livre de problemas em cenários comuns. Essas otimizações incluem:

  • Inferir tipos de parâmetros e tipos de retornos do contexto.

  • Retorno implícito de closures com uma única expressão.

  • Atalhos para nomes de argumentos

  • Sintaxe para closures do tipo Trailing.

Expressões Closure

Funções aninhadas, como introduzidas na seção Funções, são um meio conveniente de nomear e definir blocos de código autônomos como parte de uma função maior. No entanto, às vezes é útil escrever versões mais curtas de funções sem uma declaração e um nome completo. Isto é particularmente necessário quando você trabalha com funções ou métodos que tomam funções como um ou mais de seus argumentos.

Expressões closures são uma maneira de escrever closures inline (closures que são escritas no momento do uso) em uma sintaxe breve e focada. As expressões closures fornecem várias otimizações de sintaxe para escrever closures de forma abreviada sem perda de clareza ou intenção. Os exemplos de closures abaixo ilustram essas otimizações refinando um único exemplo do método sorted(by :) em várias iterações, cada um dos quais expressa a mesma funcionalidade de uma maneira mais sucinta.

O método sorted

A biblioteca padrão do Swift fornece para você um método chamado sorted(by:), que ordena um array de valores de um tipo conhecido baseado em uma closure passada como argumento para ele. Uma vez que ele completa o seu processo de ordenação, o método sorted(by:) retorna um array do mesmo tipo e tamanho do antigo array, com os seus elementos na ordem correta. O array original não é modificado pelo método sorted(by:).

A expressão closure nos exemplos abaixo usam o método sorted(by:) para ordenar um array de valores do tipo String em ordem alfabética reversa. Aqui está o array original que será ordenado:

O metodo sorted(by:) recebe uma closure que recebe dois argumentos do mesmo tipo do array e retorna um Bool que diz se o primeiro elemento deve vir antes ou depois do segundo valor uma vez que eles estão sendo ordenados Ela precisa retornar true se o primeiro valor deve aparecer antes do segundo valor, e false caso contrário.

O exemplo abaixo está ordenando um array de valores do tipo String, portanto a closure precisa ser uma função do tipo (String, String) -> Bool.

Uma maneira de fornecer a closure de ordenação é escrever uma função normal do tipo correto e passá-la como argumento para o método sorted(by:).

Se o primeiro valor string (s1) for maior que o segundo valor string (s2), a função backward(s1: s2:) irá retornar true, indicando que o valor de s1 deve aparecer antes de s2 no array ordenado. Para caracteres em valores do tipo String, o operador > significa "aparecer depois no alfabeto que:". Isso significa que a letra "B" é maior que a letra "A" e a string "Tom" é maior que a string "Tim". Isso nos dá uma ordem alfabética reversa, com "Barry" sendo armazenado antes de "Alex" e assim por diante.

No entanto, esta é uma maneira muito longa de escrever o que basicamente é uma função com uma única expressão (a > b). Neste caso seria melhor escrever usando uma closure inline, usando a sintaxe de uma expressão closure.

Sintaxe de Expressões Closure

Em geral, a sintaxe de uma expressão closure tem a seguinte forma:

Os parâmetros em uma expressão closure podem ser do tipo in-out, mas eles não podem ter um valor padrão. Parâmetros variadic podem ser usados se você der um nome para ele. Tuplas também podem ser usadas como tipos de parâmetros e tipos de retorno.

O exemplo abaixo mostra uma expressão closure para a função backward(s1: s2:) mostrada anteriormente:

Note que a declaração dos parâmetros e o tipo de retorno para essa closure inline são idênticos a declaração da função backward(s1: s2:). Em ambos os casos, elas são escritas como (s1: String, s2: String) -> Bool. Entretanto, para as expressões closure inline, os parâmetros e o tipo de retorno são escritos dentro das chaves, não fora delas.

O começo do corpo da closure começa depois do comando in. Esse comando indica que a definição dos parâmetros e tipo de retorno da closure acabou e o corpo dela vai começar. Como o corpo da closure é muito curto, ela pode ser escrita em uma única linha:

Isso mostra que a chamada geral para o método sorted(by:) permaneceu a mesma. Um par de parenteses ainda envolve o argumento do método, mesmo que esse argumento agora seja uma closure inline.

Inferindo tipo pelo contexto

Como a closure de ordenação é passada como um argumento para um método, o Swift pode inferir os tipos dos seus parâmetros e o tipo do valor de retorno. A função sorted(by:) esta sendo chamada em um array de valores do tipo String, então seu argumento deve ser uma função do tipo (String, String) -> Bool. Isso significa que os tipos (String, String) e Bool não precisam ser escritos como parte da definição da closure. Como todos os tipos podem ser inferidos, a flecha de retorno -> e os parenteses em volta dos nomes dos parâmetros também podem ser omitidos:

Sempre é possível inferir os tipos dos parâmetros e o tipo de retorno ao passar uma closure para uma função ou método como uma expressão closure inline. Como resultado, voce nunca precisa escrever uma closure inline em sua forma completa quando ela é usada como argumento para uma função ou método.

No entanto, você ainda pode tornar os tipos explícitos se desejar, ao fazer isso você evita que quem ler seu código sofra com ambiguidade e dificuldade de entendimento. No caso do método sorted(by:), o propósito da closure é claro pelo fato de que a ordenação está acontecendo, e é seguro para o leitor do código assumir que a closure provavelmente funcionará com os valores String, porque ele está executando a ordenação de um array de Strings.

Retorno implícito de expressões closure com uma única linha

Expressões closure com uma única linha podem retornar implicitamente o resultado da sua única expressão omitindo o comando return da sua declaração, como nessa versão do exemplo anterior:

Aqui, o tipo da do argumento do método sorted(by:) mostra que um valor do tipo Bool deve ser retornado da closure. Como o corpo da closure tem uma única expressão (s1 > s2) e retorna um valor Bool não há ambiguidade e o comando return pode ser omitido.

Atalhos para nomes de argumentos

O Swift fornece automaticamente atalhos para nomes de parâmetros para closures inline, que podem ser usados para referenciar os valores dos argumentos da closure pelos nomes $0, $1, $2 e assim por diante.

Se você estiver usando esses atalhos dentro da sua expressão closure, você pode omitir a definição da lista de parâmetros da definição da closure em si. O número e o tipo do argumento será inferido a partir tipo da função esperada. O comando in também pode ser omitido, porque a expressão closure é criada inteira dentro de seu próprio corpo:

Aqui, $0 e $1 fazem referência ao primeiro e segundo argumentos do tipo String da closure.

Operadores como métodos

Existe uma maneira ainda mais curta de escrever a expressão closure acima. O tipo String do Swift define sua própria implementação do operador "maior que" > como um método que recebe dois parâmetros do tipo String e retorna um valor do tipo Bool. Isto bate exatamente com o tipo necessário pelo método sorted(by:), portanto, você pode simplesmente passar um operador "maior que" e o Swift vai inferir que você quer usar a implementação do tipo String.

Para saber mais sobre operadores como métodos, veja a seção Operadores Avançados.

Closures do tipo Trailing

Se você precisa passar uma closure para uma função como último argumento e essa closure pode ser longa, pode ser útil escrevê-la como uma closure do tipo trailing. Uma closure do tipo trailing é escrita depois dos parênteses da função, mesmo que ela seja um argumento da função. Quando você usa a sintaxe de uma closure do tipo trailing, não é escrito o apelido do argumento para a closure como parte da chamada da função.

A closure de ordenação de strings da seção acima pode ser escrita fora dos parênteses método sorted(by:) como uma closure do tipo trailing.

Se uma closure é fornecida para uma função ou método como único argumento e você fornece essa expressão como uma closure do tipo trailing, você não precisa escrever um par de parênteses () depois do nome da função ou método quando estiver chamando a função.

Closures do tipo trailing são mais poderosas quando a closure é suficientemente grande que não é viável escrevê-la como uma closure inline em uma única linha. Como um exemplo, o tipo Array do Swift tem uma método chamado map(_:) que recebe uma closure como seu único argumento. A closure é chamada uma vez para cada item no array, e retorna um valor alternativo mapeado (possivelmente de algum outro tipo) para aquele item. A natureza do mapeamento e o tipo do valor retornado são deixados para a closure especificar.

Depois de aplicar o processamento da closure para cada item do array, o método map(_:) retorna um novo array contendo todos os novos valores mapeados, na mesma ordem que seus valores correspondentes no array original.

Aqui está como você pode usar o método map(_:) com uma closure do tipo trailing para converter um array com valores o tipo Int em um array com valores do tipo String. O array [16, 58, 510] é usado para criar um novo array ["OneSix" , "FiveEight", ""FiveOneZero"]:

O código acima cria um dictionary de valores mapeados de dígitos inteiros para uma valores strings com as palavras em inglês correspondente para cada nome. Ele também define um array de inteiros pronto para ser convertido.

Agora você pode usar o array numbers para criar um array de valores do tipo String passando uma closure para o método map(_:) do array como uma closure do tipo trailing:

O método map(_:) chama a closure uma vez para cada item no array. Você não precisa especificar o tipo do parâmetro de input (number) porque o tipo pode ser inferido dos valores que estão no array que será mapeado.

Neste exemplo, a variável number é inicializada com o valor do parâmetro da closure number para que o valor possa ser modificado dentro do corpo da closure (Os parâmetros para funções e closures são sempre constantes). A expressão closure também especifica um tipo de retorno String para indicar que ele será armazenado no array que será mapeado.

A expressão closure define uma string chamada output cada vez que é chamada. Ela calcula o último dígito armazenado em number usando o operador de resto (numbero % 10) e usa esse dígito para pegar um valor do apropriado dentro do dictionary digitNames. A closure pode ser usada para criar uma representação em texto de qualquer valor inteiro maior que zero.

Nota

A chamada para o subscript do dictionary digitNames é seguido por um ponto de exclamação ! porque subscripts de dictionaries retornam um valor opcional para indicar que a procura por um valor dentro do dictionary pode falhar se a chave não existir. No exemplo acima, é garantido que number % 10 sempre vai retornar uma chave válida para realizar um subscript, portanto o ponto de exclamação é usado para forçar o desembrulho do valor do tipo String armazenado no valor opcional retornado pelo subscript.

A string retornada do dictionary digitNames é adicionado no final da string output, eficientemente construindo uma string com a versão do número. (A expressãonumber % 10 nos da um valor de 6 para 16, 8 para 58 e 0 para 510) .

A variável number é então dividida por 10, como é um valor inteiro, é arredondado durante a divisão, portanto 16 se transforma em 1, 58 em 5 e 510 em 51.

O processo é repetido até que o valor de number seja igual a 0, nesse ponto a string output é retornada pela closure e é adicionada ao array de saída pelo método map(_:).

O uso da sintaxe de uma closure do tipo trailing no exemplo acima encapsula ordenadamente a funcionalidade da closure imediatamente após a função em que a closure atua, sem a necessidade de escrever a closure inteira dentro dos parênteses do método map(_:).

Capturando Valores

Uma closure pode capturar constantes e variáveis do contexto ao redor em que está definida. A closure pode então referenciar e modificar os valores dessas constantes e variáveis dentro do seu corpo, mesmo se o escopo original que definiu essas constantes e variáveis não existe mais.

No Swift, a forma mais simples de uma closure que pode capturar valores é uma função aninhada, escrita dentro do corpo de uma outra função. Uma função aninhada pode capturar qualquer um dos argumentos da função que a engloba e também pode capturar qualquer uma das constantes e variáveis definidas na função que a engloba.

Abaixo temos um exemplo de uma função chamada makeIncrementer, que contem uma função aninhada chamada incrementer A função aninhada incrementer() captura dois valores, runningTotal e amount, do contexto ao seu redor. Depois de capturar esses valores, a função incrementer é retornada pela função makeIncrementer como uma closure que incrementar o valor de runningTotal pelo valor de amount cada vez que é chamada.

O tipo de retorno da funçao makeIncrementer é () -> Int. Isso significa que ela retorna uma função ao invés de um valor simples. A função que ela retorna não tem parâmetros e retorna um valor do tipo Int toda vez que é chamada. Para aprender como funções podem retornar outras funções, veja a seção Funções.

A função makeIncrementer(forIncrement:) define uma variável inteira chamada runningTotal, para armazenar o valor total atual do incrementer que será retornado. Essa variável é inicializada com o valor 0.

Ela tem um único parâmetro do tipo Int com um apelido de argumento forIncrement e o nome de parâmetro amount. O valor do argumento passado para esse parâmetro especifica quanto o valor de runningTotal deve ser incrementado a cada vez que a função incrementer retornada for chamada. A função makeIncrementer define uma função aninhada chamada incrementer, que executa a incrementação. Essa função simplesmente adiciona o valor de amount para runningTotal e retorna o resultado.

Quando considerada em um caso isolado, a função aninhada incrementer() parece inútil.

A função incrementer() não recebe nenhum parâmetro e mesmo assim ainda faz referência para runningTotal e amount do corpo da função em que está definida. Ela faz isso capturando uma referência para runningTotal e amount do contexto da função e usando eles dentro do seu próprio corpo. Capturar por referência garante que runningTotal e amount não vão desaparecer quando a chamada para makeIncrementer terminar, e também garante que runningTotal estará disponível na próxima vez que a função incrementer for chamada.

Nota

Como uma otimização, o Swift pode capturar e armazenar uma cópia de um valor se esse não for alterado pela closure, e se o valor não for alterado depois que a closure é criada.

O Swift também cuida de todo gerenciamento de memória envolvido em liberar variáveis da memória quando elas não são mais ncessárias

Aqui temos um exemplo da função makeIncrementer em ação:

Este exemplo cria uma constante chamada incrementByTen para referenciar uma função incrementer que adiciona 10 ao valor da variável runningTotal toda vez que é chamada. Chamar a função várias vezes mostra este comportamento em ação:

Se você criar um segundo incrementer, ele terá sua própria referência armazenada para uma nova variável runningTotal:

Chamar o incrementByTen novamente continua a incrementar sua própria variável runningTotal e não afeta a variável capturada pelo incrementBySeven:

Nota

Se você passar uma closure para uma propriedade de uma instância de uma classe, e a closure capturar essa instância se referindo a instância ou aos seus membros, você vai criar um clico de referência forte entre a closure e a instância. O Swift usa listas de capturas para quebrar esses ciclos de referência forte. Para mais informações veja a seção e Contagem Automática de Referência.

Closures são tipos de referência

Nos exemplos acima, incrementBySeven e incrementByTen são constantes, mas as closures que essas constantes referenciam ainda são capazes de incrementar as variáveis runningTotal que elas capturaram. Isso acontece porque funções e closures são tipos de referência.
Sempre que você passa uma função ou uma closure para uma constante ou uma variável, na verdade você está setando essa constante ou variável para ser uma referência para a função ou closure. No exemplo acima, é uma escolha da closure que incrementByTen seja referenciado por uma constante, e não o conteúdo da closure em si.

Isso também significa que se você atribuir uma closure para duas constantes ou variáveis diferentes, ambas irão referenciar para a mesma closure.

Escapando closures

Uma closure pode escapar de uma função quando a closure é passada como argumento para essa função, mas é chamada depois que a função retorna. Quando você declara uma função que recebe uma closure como um de seus parâmetros, você pode esrever o comando @escaping antes do tipo do parâmetro para indicar que essa closure pode escapar.

Um dos jeitos que a closure pode escapar é sendo armazenada em uma variável que está declarada fora da função. Como exemplo, muitas funções que começam uma tarefa asíncrona recebem uma closure como argumento como um callback para o processo completo. A função retorna depois que começou sua operação, mas a cloure não é chamada até que a operação esteja completa, neste caso a closure precisa escapar para ser chamada depois. Por exemplo:

A função someFunctionWithEscapingClosure(_:) recebe uma closure como argumento e adiciona ela em um array que é declarado fora da função. Se você não marcar o parâmetro dessa função com o comando @escaping, você receberá um erro em tempo de compilação.

Marcar uma closure com o comando @escaping significa que você precisa fazer referência usando o comando self explicitamente dentro da closure. Por exemplo, no código abaixo, a closure passada para someFunctionWithEscapingClosure(_:) é uma closure que pode escapar, o que significa que ela precisa fazer referência usando o comando self explicitamente. Em contraste, a closure passada para someFunctionWithNonescapingClosure(closure:) é uma closure que não pode escapar, o que significa que ela pode fazer referência sem usar o comando self explicitamente.

Autoclosures

Uma autoclosure é uma closure que é automaticamente criada para embrulhar uma expressão que está sendo passada como argumento para uma função. Ela não recebe nenhum parâmetro e quando é chamada, ela retorna o valor da expressão armazenado dentro de si. Essa conveniência sintática permite que voce omita as chaves ao redor dos parâmetros da função escrevendo uma expressão normal ao invés de uma closure explicita.

É normal chamar funções que recebem autoclosures, mas não tão comum implementar esse tipo de função. Por exemplo, a função assert(condition: message: file: line:) recebe uma autoclosure para os parâmetros condition e message, seu parâmetro condition só é processado em builds do tipo debug e seu parâmetro message é processado se a condição for false.

Uma autoclosure permite que você atrase a execução, porque o código não irá rodar até que voce chame a closure. Atrasar o execução é útil para códigos que tem efeitos colaterais ou são computacionalmente grandes, porque deixa você controlar quando o código será executado. O código abaixo mostra como esse processo funciona:

Mesmo que o primeiro elemento do array customersInLine seja removido pelo código dentro da closure, o elemento do array não é removido até a closure ser realmente chamada. Se a closure nunca for chamada, a expressão dentro dela nunca será executada, o que quer dizer que o elemento do array nunca será removido. Note que o tipo do customerProvider não é String, mas sim () -> String, ou seja, uma função que não recebe parâmetros e retorna uma String.
Você tem o mesmo comportamento de execução atrasada qando você passa uma closure como argumento para uma função:

A função serve(customer:) no exemplo acima recebe uma closure explícita que retorna o nome do cliente. A versão de serve(customer:) abaixo executa a mesma operação, mas ao invés de receber uma closure explícita, ela recebe uma autoclosure adicionando o comando @autoclosure na frente do tipo do parâmetro. Agora você pode chamar a função como se ela recebesse uma String no lugar de uma closure. O argumento é automaticamente convertido em uma closure, porque o tipo do parâmetro customerProvider está marcado com o comando @autoclosure.

Nota

Usar o recurso de autoclosure sem necessidade pode deixar o seu código difícil de entender O contexto e o nome da função devem deixar claro que a execução será adiada.

Se você precisa que uma autoclosure possa escapar, use ambos os comandos @autoclosure e @escaping.

No código acima, ao invés de chamar a closure passada para a função com o nome customerProvider, a função collectCustomerProviders(_:) adiciona a closure ao array customerProviders. O array é declarado fora do corpo da função, o que significa que a closure dentro do array pode ser executada depois que a função retornar. Como resultado, o valor do parâmetro customerProvider deve poder escapar do corpo da função.

results matching ""

    No results matching ""