Quando o Swift foi apresentado pela Apple em 2014, ele tinha como objetivo atender a todas as demandas que os engenheiros de software tinham por linguagens de programação modernas. Chris Lattner , que projetou o Swift na Apple, tinha o objetivo de fazer uma linguagem que pudesse ser usada tanto no ensino de programação quanto na construção de software para sistemas operacionais.

Desde então, a Apple abriu o código-fonte da linguagem e, como resultado, ela continua a evoluir. Apesar das melhorias feitas no Swift, um recurso importante que ainda está faltando são os primitivos para simultaneidade e paralelismo.

No passado, você podia imitar primitivos em Swift usando bibliotecas como Grand Central Dispatch (GCD) e libdispatch. Hoje em dia, podemos impor primitivas para simultaneidade usando as palavras-chave async e await .

Neste tutorial, discutiremos o que é simultaneidade e por que ela é útil. Em seguida, aprenderemos a usar as palavras-chave async e await para impor a simultaneidade.

Vamos começar!

Concorrência e núcleos de CPU

Devido às mudanças feitas nos processadores na última década, simultaneidade tornou-se um tópico mais relevante na programação de computadores. Apesar de um aumento no número de transistores em processadores mais novos, não houve uma melhoria significativa na velocidade do clock.

No entanto, uma melhoria notável nos processadores é a presença de mais núcleos de CPU em cada chip. Os processadores mais recentes da Apple, como o A14, encontrado no iPhone 12, têm seis núcleos de CPU . O processador M1, usado em Macs e no iPad, tem oito núcleos de CPU . No entanto, a velocidade do clock do A14 ainda está em torno de 3,1 GHz.

Os verdadeiros avanços no design da CPU vieram da alteração do número de núcleos nos chips modernos. Para tirar proveito desses processadores mais novos, precisamos melhorar nossas habilidades em programação simultânea.

Tarefas de longa duração

Na maioria dos sistemas de computador modernos, o thread principal é usado para renderizar e manipular a interface do usuário e as interações do usuário. Os desenvolvedores de iOS costumam enfatizar a necessidade de nunca bloquear o thread principal.

Tarefas de longa execução, como fazer uma solicitação de rede, interagir com um sistema de arquivos ou consultar um banco de dados podem bloquear o thread principal, fazendo com que a IU de um aplicativo congele. Felizmente, a Apple forneceu várias ferramentas diferentes que podemos usar para evitar o bloqueio da IU de um aplicativo.

Opções de simultaneidade em Swift

Melhorias em frameworks como GCD e libdispatch tornaram a programação simultânea muito mais fácil.

A prática recomendada atual para dispositivos iOS é descarregar qualquer tarefa que bloqueie o thread principal para um thread de segundo plano ou uma fila. Depois que a tarefa é concluída, os resultados geralmente são tratados em bloco ou fechamento final.

Antes do lançamento do GCD, a Apple fornecia APIs que usavam delegação para descarregar tarefas. Primeiro, um desenvolvedor teve que executar um thread separado para um objeto delegado, que chamou um método na classe de chamada para lidar com a conclusão da tarefa.

Embora o descarregamento de uma tarefa funcione, ler esse tipo de código pode ser difícil, e quaisquer erros permitem a introdução de novos tipos de bugs. Portanto, em 2017, Chris Lattner escreveu seu Manifesto de simultaneidade do Swift , que expressou suas ideias sobre como adicionar simultaneidade ao Swift usando assíncrono/espera.

Grand Central Dispatch

O GCD, apresentado pela primeira vez em 2009, é o método da Apple para gerenciar o paralelismo de tarefas por meio de um pool de thread gerenciado nos sistemas operacionais da Apple.

A implementação do GCD teve origem em uma biblioteca C, permitindo que os desenvolvedores a usassem com C, C ++ e Objective-C. Depois que o Swift foi introduzido, um wrapper Swift para GCD foi criado para desenvolvedores que usam a linguagem mais recente da Apple.

O GCD também foi portado para libdispatch, que é usado em outro software de código aberto. O Apache Web Server incorporou esta biblioteca para multiprocessamento.

Grand Central DispatchQueue

Vamos ver o GCD em ação! Usaremos GCD para atribuir trabalho a outra fila de despacho. No snippet de código abaixo, uma função está atribuindo parte de seu trabalho a uma tarefa assíncrona:

 rápido
função doSomethinginTheBackground () { DispatchQueue.global (qos:.background).async { //Faça um trabalho de longa duração aqui ... }
}

A classe DispatchQueue fornece métodos e propriedades que permitem aos desenvolvedores executar o código em um fechamento final. Um cenário comum é executar uma tarefa de longa execução em um encerramento final que produz algum tipo de resultado e, em seguida, retornar esse resultado de volta ao encadeamento principal.

No trecho de código abaixo, o DispatchQueue está fazendo algum trabalho antes de retornar um resultado para o thread principal:

 rápido
DispatchQueue.global (qos:.background).async { //Faça algum trabalho aqui DispatchQueue.main.async { //retorna ao tópico principal. print ("Trabalho concluído e de volta ao tópico principal!") }
}

Um cenário mais comum seria fazer uma chamada de rede usando NSURLSession , manipular os resultados em um encerramento final e, em seguida, retornar ao thread principal:

 rápido
func goGrabSomething (completed: @escaping (MyJsonModel ?, Error?)-> Void) { deixe ourl=URL (string:"https://mydomain.com/api/v1/getsomejsondata") se deixar url=ourl { let req=URLRequest (url: url) URLSession.shared.dataTask (with: req) {data, _, err in guarda let data=data, err==nil else { Retorna } Faz { let model=try JSONDecoder (). decode (MyJsonModel.self, from: data) DispatchQueue.main.async { conclusão (modelo, nulo) } } pega { conclusão (nulo, erro) } }.retomar() }
}

Embora o exemplo acima seja compilado e executado, existem vários bugs. Por um lado, não estamos usando manipuladores de conclusão em todos os lugares em que a função pode sair. Também é mais difícil de ler ao escrever código de forma síncrona.

Para melhorar o código acima, usaremos async e await .

Usando async/await em seu código

Quando o iOS 15 e o macOS 12 forem lançados em outono de 2021 , os desenvolvedores poderão usar a nova sintaxe async/await. Você já pode usar async/await em linguagens como JavaScript e C #.

Essas duas palavras-chave estão se tornando a melhor prática para os desenvolvedores escreverem código simultâneo em linguagens de programação modernas. Vamos dar uma olhada na função anterior goGrabSomething , reescrita usando a nova sintaxe async/await:

 rápido
função goGrabSomething () async throws-> MyJsonModel? { var model: MyJsonModel?=nulo deixe ourl=URL (string:"https://mydomain.com/api/v1/getsomejsondata") se deixar url=ourl { let req=URLRequest (url: url) let (data, _)=try await URLSession.shared.data (para: req) model=try JSONDecoder (). decode (MyJsonModel.self, from: data) } modelo de retorno
}

No exemplo acima, adicionamos a palavra-chave async antes de throws e depois do nome da função. Se nossa função não disparasse, async iria antes de -> .

Consegui alterar a assinatura da função para que ela não precise mais ser completada. Agora, podemos retornar o objeto que foi decodificado de nossa chamada de API.

Dentro de nossa função, estou usando a palavra-chave await na frente de minha URLSession.shared.data (for: URLRequest) . Como a função de dados URLSession pode gerar um erro, coloquei um try na frente da palavra-chave await .

Cada vez que usamos um await no corpo da nossa função, ele cria uma continuação. Se o sistema tiver que esperar quando processar nossa função, ele pode suspender nossa função até que esteja pronto para retornar de seu estado suspenso.

Se tentarmos chamar a função goGrabSomething a partir do código síncrono, ocorrerá uma falha. O Swift oferece uma boa solução alternativa para esse caso de uso! Podemos usar um encerramento async em nosso código síncrono para chamar nossas funções async :

 rápido
assíncrono { var myModel=try await goGrabSomething () print ("Nome: \ (meuModelo.nome)")
}

Agora, o Swift tem seu próprio sistema de gerenciamento de simultaneidade e paralelismo. Aproveitando essas novas palavras-chave, podemos aproveitar as vantagens dos novos recursos de simultaneidade no sistema.

O resultado final é que podemos escrever uma função que é mais fácil de ler e contém menos código.

Conclusão

Async/await em Swift simplifica muito a maneira como escrevemos código simultâneo em aplicativos iOS. Você pode brincar com esses novos recursos baixando o Xcode 13 e executando esses exemplos nas versões beta do iOS 15 e macOS 12.

Este artigo abordou apenas a superfície do que é possível com esses novos recursos. Por exemplo, o Swift também adicionou um tipo de objeto actor que permite aos desenvolvedores criar objetos write que contêm o estado mutável compartilhado, que pode ser usado em tópicos sem condições de corrida.

Espero que tenha gostado deste artigo. Se você estiver interessado em aprender mais sobre async/await no Swift, assista à apresentação WWDC21 da Apple .

A postagem Simultaneidade em Swift: usando a nova sintaxe async/await apareceu primeiro no LogRocket Blog .