idiomas que suportam funções de primeira classe permitem que você use funções e métodos como qualquer outro objeto ou valor. Você pode passá-los como argumentos, salvá-los em Propriedades ou devolvê-los de outra função. Em ordem de palavras, a linguagem trata funções como “cidadãos de primeira classe”.

embora o Swift dificilmente seja a primeira linguagem a suportar essa maneira de lidar com funções, normalmente é um recurso que você vê em linguagens mais dinâmicas, como JavaScript ou Lua. Portanto, combinar o sistema de tipo estático robusto do Swift com funções de primeira classe torna-se uma combinação bastante interessante e pode nos permitir fazer algumas coisas muito criativas 😀.

Esta semana, vamos dar uma olhada em algumas maneiras diferentes que as funções de primeira classe podem ser usadas no Swift!

Instabug: resolva bugs, travamentos e outros problemas muito mais rapidamente usando os rastreamentos detalhados de pilha, logs de rede e eventos de interface do usuário que o Instabug anexa automaticamente a cada relatório de bug. Usado por mim e por milhares de equipes de desenvolvimento iOS em todo o mundo. Experimente gratuitamente e integre-o com apenas uma única linha de código.

passando funções como argumentos

vamos começar com o básico. Como as funções podem ser usadas como valores, isso significa que podemos passá-las como argumentos. Por exemplo, digamos que queremos adicionar uma matriz de subvisualizações a uma visualização. Normalmente, podemos fazer algo assim:

let subviews = subviews.forEach { subview in view.addSubview(subview)}

o código acima funciona e não há nada de errado com isso. Mas se aproveitarmos as funções de primeira classe, podemos realmente reduzir bastante sua verbosidade.

o que podemos fazer é tratar o método addSubview como um fechamento do tipo (UIView) -> Void (uma vez que aceita uma visualização para adicionar e não retorna nada). Isso corresponde perfeitamente ao tipo de argumento que forEach aceita (um fechamento do tipo (Element) -> Void e, neste caso, o tipo Element é UIView). O resultado é que podemos passar view.addSubview diretamente como um argumento para nossa chamada forEach, assim:

subviews.forEach(view.addSubview)

isso é muito legal! 😎 No entanto, uma coisa a ter em mente é que, ao usar métodos de instância como fechamentos como esse, você mantém a instância automaticamente, desde que mantenha o fechamento. Isso não é um problema ao passar uma função como um argumento de fechamento que não escapa como acima, mas para escapar de fechamentos é algo a ser percebido para evitar reter ciclos.

para obter mais informações sobre como escapar vs argumentos de fechamento não escapando e capturar, confira “capturando objetos em fechamentos rápidos”.

passando inicializadores como argumentos

o legal é que não são apenas funções e métodos que podem ser usados como funções de primeira classe no Swift – você também pode usar inicializadores dessa maneira.

Por exemplo, digamos que temos uma matriz de imagens que gostaríamos de criar visualizações de imagens, e que queremos adicionar cada uma dessas visualizações de imagens para uma pilha de vista. Usando funções de primeira classe, podemos alcançar todos os itens acima usando uma cadeia simples de map e forEach:

images.map(UIImageView.init) .forEach(stackView.addArrangedSubview)

o que eu gosto em estruturar o código dessa maneira é que ele se torna muito declarativo. Em vez de loops aninhados for, estamos simplesmente declarando o que queremos que o resultado seja. É claro que há um equilíbrio a ser alcançado entre Código declarativo e compacto e legibilidade, mas para operações simples como as acima, acho que aproveitar as funções de primeira classe pode ser super agradável.

você pode encontrar mais exemplos e casos de uso para passar inicializadores como fechamentos em “Simple Swift dependency injection with functions” E “time traveling in Swift unit tests”.

criando referências de método de instância

vamos mergulhar um pouco mais fundo no maravilhoso mundo das funções de primeira classe 😉. Uma coisa que me intrigou por mais tempo foi o fato de que recebi sugestões de Conclusão automática do método de instância quando queria chamar um método estático. Tente digitar UIView. no Xcode para ver o que quero dizer, você obtém cada método de instância como uma sugestão 🤔.

no começo eu pensei que este era um bug Xcode, mas então eu decidi investigá-lo. Acontece que, para cada método de instância que um tipo possui, há um método estático correspondente que permite recuperar esse método de instância como um encerramento, passando uma instância como um argumento.

Por exemplo, podemos usar o seguinte para obter uma referência para o removeFromSuperview método para um determinado UIView exemplo:

let closure = UIView.removeFromSuperview(view)

Chamando acima de encerramento seria exatamente o mesmo que chamar view.removeFromSuperview(), o que é interessante, mas é realmente útil? Vamos dar uma olhada em alguns cenários em que usar esse recurso pode realmente levar a alguns resultados muito legais.

XCTest no Linux

uma maneira que uma das estruturas da Apple usa esse recurso é ao executar testes usando XCTest no Linux. Nas próprias plataformas da Apple, o XCTest funciona usando o tempo de execução Objective-C para procurar todos os métodos de teste para um determinado caso de teste e, em seguida, executá-los automaticamente. No entanto, no Linux não há tempo de execução Objective-C, por isso requer que escrevamos um pouco de clichê para fazer nossos testes serem executados.

primeiro, temos que declarar um dicionário estático allTests que contém um mapeamento entre nossos nomes de teste e os métodos reais para executar:

passamos o dicionário acima para a função XCTMain para executar nossos testes:

XCTMain()

sob o capô, isso está usando o recurso de poder extrair métodos de instância usando seu equivalente estático, o que nos permite simplesmente nos referir às funções pelo nome em um contexto estático, enquanto ainda permite que a estrutura gere métodos de instância para serem executados. Muito inteligente! 👍

sem esse recurso, teríamos que escrever algo assim:

chamando um método de instância em cada elemento em uma sequência

vamos pegar esse recurso para um giro nós mesmos. Assim como conseguimos passar o método de instância de outro objeto como um argumento para forEach, não seria legal se também pudéssemos passar um método de instância que queremos que cada elemento em uma sequência seja executado?

por exemplo, digamos que temos uma matriz de subviews que queremos remover de sua superview. Em vez de ter que fazer isso:

for view in views { view.removeFromSuperview()}

não Seria legal se pudéssemos fazer isso em vez:

views.forEach(UIView.removeFromSuperview)

A boa notícia é que podemos, todos nós temos que fazer é criar uma pequena extensão em Sequence que aceita um desses estaticamente referenciado métodos de instância. Uma vez que são funções que geram uma função (Functionception! 😂 ) seu tipo sempre será (Type) -> (Input) -> Output, portanto, para nossa extensão, podemos criar uma sobrecarga forEach que aceita um fechamento desse tipo:

agora podemos facilmente chamar métodos de instância em cada membro de qualquer sequência! Implementing

implementando target/action sem Objective-C

vamos dar uma olhada em mais um exemplo. No UIKit, o padrão alvo / ação é muito comum, para tudo, desde observar cliques de botão até responder a gestos. Eu pessoalmente gosto muito desse padrão, pois ele nos permite usar facilmente um método de instância como um retorno de chamada sem ter que nos preocupar com o problema de ciclo de retenção que discutimos anteriormente (ao referenciar um método de instância como um encerramento).

no entanto, a forma como o target/action é implementado no UIKit depende de seletores Objective-C (é por isso que você precisa anotar métodos de ação privada com @objc). Digamos que queríamos adicionar o padrão de destino / ação a uma de nossas visualizações personalizadas e, digamos, queremos fazê-lo sem depender de seletores Objective-C. Isso pode soar como muito trabalho e que tornará as coisas muito complicadas, mas graças às funções de primeira classe – é bastante simples! 😀

vamos começar definindo um Action typealias como uma função estática que retorna um método de instância para um determinado tipo e entrada:

typealias Action<Type, Input> = (Type) -> (Input) -> Void

em seguida, vamos criar nossa visão. Criaremos um ColorPicker que permite ao usuário escolher uma cor em um aplicativo de desenho e adicionar um método para adicionar uma ação target & a ele. Manteremos o controle de todas as observações como fechamentos, e toda vez que um fechamento é executado, geramos um método de instância para o destino dado e o executamos, assim:

o legal é que podemos realmente usar funções de primeira classe ainda mais acima. Usando a API map em Optional, podemos gerar o método de instância e chamá-lo de uma só vez, assim:

observations.append { view in target.map(action)?(view)}

finalmente, vamos usar nossa nova API target / action em um CanvasViewController, que apresentará nosso ColorPicker. Assim como nós, adicionar um destino & ação UIButton ou UIGestureRecognizer, podemos simplesmente passar a ver controlador de si mesmo e um método de instância para executar, como este:

Tipo de seguro metas & ações, sem qualquer objective-C seletores ou riscos de fugas de memória, usando apenas algumas linhas de código – muito legal! Support

suporte Swift por Sundell, verificando este patrocinador:

Instabug: resolva bugs, travamentos e outros problemas muito mais rapidamente usando os rastreamentos detalhados de pilha, logs de rede e eventos de interface do usuário que o Instabug anexa automaticamente a cada relatório de bug. Usado por mim e por milhares de equipes de desenvolvimento iOS em todo o mundo. Experimente gratuitamente e integre-o com apenas uma única linha de código.

conclusão

funções de primeira classe é um recurso muito poderoso. Ao ser capaz de usar funções e métodos de uma forma muito mais dinâmica, podemos alcançar alguns resultados bastante interessantes, e pode ser realmente útil ao implementar certos tipos de APIs.

no entanto, nas famosas palavras do Tio Ben; com grande poder vem grande responsabilidade. Embora eu ache super útil aprender sobre esses tipos de recursos e como eles funcionam, também é importante exercer alguma restrição ao usá-los. Nosso objetivo deve sempre ser criar APIs que sejam agradáveis e fáceis de usar, e escrever código que seja fácil de ler & manter. As funções de primeira classe podem definitivamente nos ajudar a cumprir esse objetivo, mas se levadas longe demais, também podem levar ao contrário. Como sempre, minha recomendação é experimentar, experimentar esses recursos e ver por si mesmo se e como eles podem ser usados em seu próprio código.

Deixe uma resposta

O seu endereço de email não será publicado.