Compreender Swift Closures

Jimmy M Andersson Blocked Desbloquear Seguir Seguindo 7 de janeiro

No artigo anterior , discutimos a Programação Orientada por Protocolo e quais vantagens ela traz para a mesa para nós. Hoje damos uma olhada no Swift Closures e nos perguntamos como podemos alavancar sua sintaxe curta e concisa para melhorar nosso código.

O que são fechamentos?

“Fechamentos são blocos autocontidos de funcionalidade que podem ser passados e usados em seu código. Fechamentos no Swift são similares aos blocos em C e Objective-C e aos lambdas em outras linguagens de programação. ”

A citação acima vem direto do Swift Language Guide e, embora pareça que não explica nada, é na verdade uma descrição muito precisa do que é um fechamento. Vamos dar uma olhada no que a maioria de nós provavelmente pensa quando ouvimos a palavra “encerramento”, ou seja, o encerramento final.

 URLSession.shared.dataTask (com: url) {(dados, resposta, erro) em 

// Processe todas as coisas legais que você recebeu de sua solicitação

}

Este é um tipo muito comum de encerramento final. Nesse caso, usamos uma função do Foundation Framework para fazer uma solicitação assíncrona para alguns dados que desejamos. Em seguida, usamos um encerramento final para especificar as ações que queremos executar nesses dados sempre que estiverem disponíveis.

Para desenvolvedores que não estão familiarizados com fechamentos e sua sintaxe, pode parecer que estamos apenas extraindo três parâmetros do nada e, de alguma forma, eles funcionam magicamente. Para entender por que especificamos esses nomes de parâmetro e para facilitar a declaração de nossas próprias funções com encerramentos finais, vamos dar uma olhada em uma implementação fictícia da função .dataTask(with:completionHandler:) .

 func dataTask (com url: URL, completionHandler: (dados?, URLResponse ?, erro?) -> void) { 

// Realize o trabalho para obter os dados que solicitamos
deixar dados: dados? = {...}

// Obtém a resposta da carga
deixe resposta: URLResponse? = {...}

// Se não obtivermos nenhum dado, produziremos um erro
deixe o erro: Erro? = {...}


// Nós temos todas as informações que precisamos,
// vamos enviá-lo para o nosso fechamento para processamento final

completionHandler (dados, resposta, erro)
}

Embora este seja um esboço muito grosseiro do que acontece na implementação real desta função, ela descreverá com precisão o comportamento no qual estamos interessados, ou seja, como o encerramento é incorporado e usado no fluxo de trabalho dos métodos.

Dê uma olhada na declaração do método, no parâmetro chamado completionHandler , e observe a declaração de tipo:

(Data?, URLResponse?, Error?) -> Void

Isso informa ao compilador que nosso método .dataTask espera passar outra função como argumento. Também espera que essa função leve três tipos muito específicos como seus próprios argumentos. Estes são os parâmetros que nomeamos no nosso encerramento final.

Basicamente, nosso programa informa ao URLSession para obter alguns dados e, em seguida, especifica onde deixá-lo para que possamos continuar com nossas atualizações.

DispatchQueue Closures

Nossa amada classe DispatchQueue também vem com algumas funções que tomam closures como argumentos. Veja esta .async(execute:) :

 DispatchQueue.main.async { 
// Faça atualizações e tais
}

Isso é um pouco mais complicado, porque quando você lê a documentação do DispatchQueue, ele nos diz que esse método usa um DispatchWorkItem como um argumento. Estamos tentando passar uma expressão de encerramento de type () -> Void , então por que isso funciona?

Se nos aprofundarmos na documentação, encontramos a declaração do inicializador DispatchWorkItem:

 init(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, block: @escaping () -> Void ) 

Vamos quebrar isso. Ao criar um DispatchWorkItem, passamos a ele um argumento DispatchQoS, um argumento DispatchWorkItemsFlags e uma função que não recebe argumentos e não retorna nada. O legal sobre como isso é construído é que os dois primeiros argumentos têm valores padrão, o que significa que podemos omiti-los se não quisermos alterar esses padrões. O que isto significa é que quando passamos o fechamento para o nosso .async(execute:) , ele será empacotado em um objeto DispatchWorkItem com valores padrão para o restante dos parâmetros. Agradável!

Vamos dar uma olhada rápida em uma implementação fictícia do que acontece quando a fila de distribuição executa nosso código enviado:

 deixe nextItem = getNextItemFromQueue () 
 if! nextItem.isCancelled { 
// Aqui é onde o nosso bloco de fechamento fica
// desembrulhado e executado.
nextItem.perform ()
}

A fila chama .perform() em nosso item de trabalho e não transmite nenhum argumento. Isso é exatamente o que a declaração acima nos disse, e é por isso que não precisamos nomear nenhum parâmetro em nosso encerramento final quando criamos o DispatchWorkItem. Na etapa abaixo da linha, o item de trabalho realmente fará o mesmo tipo de chamada no bloco de código real – sem argumentos e sem valores de retorno.

É isso por esta semana! Espero que essa rápida olhada nos encerramentos tenha trazido um novo entendimento de como eles são usados e como você pode incorporá-los em seus próprios projetos!

Sinta-se à vontade para comentar se tiver dúvidas e siga para receber notificações sobre artigos futuros.