SwiftUI: Escaping Closures-> Especialmente úteis para callbacks e tratamento assíncrono

📱Escaping Closures em SwiftUI

Trabalhar com operações assíncronas em Swift e SwiftU 👨🏽‍💻

Edson Santos
4 min readJul 10, 2024

--

🙋🏽‍♂️Eae pessoal, tudo bem?

Em Swift e SwiftUI as closures são blocos de código auto-contidos que podem ser passados e usados em nossos aplicativos. Elas são especialmente úteis para callbacks e tratamento assíncrono.

Um tipo particular de closure que todo desenvolvedor Swift precisa conhecer é a “escaping closure”.

🤔O que são Closures?

Closures são blocos de código que podem ser passados e usados em seu código. Eles podem capturar e armazenar referências a variáveis e constantes de fora de seu escopo. Em Swift, closures são frequentemente usados em callbacks e como handlers assíncronos.

Há três formas principais de definir e usar closures:

  1. Inline Closures: São definidos diretamente no local onde são usados. Eles são frequentemente usados como argumentos em funções.
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map { (number: Int) -> Int in
return number * 2
}
  1. Trailing Closures: Se o último parâmetro de uma função é um closure, você pode usar a sintaxe de trailing closure, que melhora a legibilidade do código, especialmente quando o closure é longo.
let doubledNumbers = numbers.map { number in
number * 2
}
  1. Named Closures: São closures atribuídos a uma variável ou constante e podem ser reutilizados.
let doubleNumber: (Int) -> Int = { number in
return number * 2
}

let doubledNumbers = numbers.map(doubleNumber)

O conceito de escaping closures se aplica a qualquer uma dessas formas, mas geralmente está mais associado a closures passados como parâmetros para funções, onde sua execução pode ocorrer após a função retornar.

🤷🏽‍♂️E o Escaping Closures ?

Estes são os que podem ser executados após a função que os recebeu como argumento ter retornado. Para marcar um closure como escaping, usa-se a anotação @escaping.

Contexto onde Escaping Closures são usados

Escaping closures são geralmente usados em operações assíncronas, como chamadas de rede, onde o closure é executado após a conclusão da operação assíncrona. Iremos ver um exemplo para clarificar melhor tudo isso.

👨🏽‍💻Exemplificando

Vamos criar um pequeno aplicativo utilizando Swift e SwiftUI que faz uma requisição a uma API pública (iremos usar a https://jsonplaceholder.typicode.com/) e exibir os dados em uma lista.

Passo 1: Configuração do Projeto

Crie um novo projeto no XCode em SwiftUI e certifique-se de que o Swift e o SwiftUI estejam selecionados. Nomeie seu projeto como “EscapingClosuresExemplo”.

Passo 2: Estrutura de projeto em camadas

Estruturar nosso projeto em camadas, como o MVVM (Model-View-ViewModel), ajuda a organizar nosso código de forma mais modular e sustentável.

Vamos organizar nosso projeto em três camadas principais:

  1. Model: Representa os dados e a lógica de negócio.
  2. View: Representa a interface do usuário.
  3. ViewModel: Faz a ponte entre a Model e a View, fornecendo dados à View e respondendo às interações do usuário.

Estrutura Completa do Projeto

A estrutura final deste nosso projeto de exemplo ficará dessa forma:

EscapingClosuresExemplo/
├── EscapingClosuresExemploApp.swift
├── Model/
│ ├── Post.swift
│ └── NetworkService.swift
├── ViewModel/
│ └── PostViewModel.swift
└── View/
└── ContentView.swift

Model

A Model contém a definição dos dados e as operações relacionadas. Vamos criar dois arquivos:

  • Post.swift: Definição do modelo de dados.
import Foundation

struct Post: Codable, Identifiable {
let id: Int
let title: String
let body: String
}
  • NetworkService.swift: Serviço de rede irá buscar os posts da API. É aqui onde usaremos um escaping closure.
import Foundation

class NetworkService {
func fetchPosts(completion: @escaping ([Post]) -> Void) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }

URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let posts = try JSONDecoder().decode([Post].self, from: data)
DispatchQueue.main.async {
completion(posts)
}
} catch {
print("Error decoding: \(error)")
}
}
}.resume()
}
}

No NetworkService, a função fetchPosts faz uma requisição a uma URL e usa um escaping closure para passar os dados de volta. O uso de @escaping indica que o closure pode ser chamado depois que a função fetchPosts termina sua execução.

ViewModel

O ViewModel é responsável por interagir com o serviço de rede e fornecer dados para a View. Vamos criar um arquivo:

  • PostViewModel.swift: Definição do ViewModel.
import Combine

class PostViewModel: ObservableObject {
@Published var posts: [Post] = []
private var networkService = NetworkService()

func getPosts() {
networkService.fetchPosts { [weak self] posts in
self?.posts = posts
}
}
}

O PostViewModel é responsável por buscar os dados e atualizá-los para a view. Ele usa o NetworkService para fazer a requisição e atualizar a propriedade posts quando os dados são recebidos.

View

A View é responsável por exibir os dados e interagir com o usuário. Vamos criar um arquivo:

  • ContentView.swift: Definição da interface do usuário.
import SwiftUI

struct ContentView: View {
@StateObject private var viewModel = PostViewModel()

var body: some View {
NavigationView {
List(viewModel.posts) { post in
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
Text(post.body)
.font(.subheadline)
}
}
.navigationTitle("Posts")
.onAppear {
viewModel.getPosts()
}
}
}
}

@main
struct EscapingClosuresExemploApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

A ContentView usa o PostViewModel para buscar e exibir os posts. Quando a view aparece (onAppear), o getPosts é chamado para iniciar a requisição.

Considerações Finais

Pessoal entender escaping closures é fundamental para trabalhar com operações assíncronas em Swift e SwiftUI. Eles permitem que você gerencie callbacks e processos que precisam continuar a execução depois que a função inicial já retornou, como requisições de rede.

Espero ter ajudado para que você possa começar a usar escaping closures em seus próximos projetos SwiftUI.

Fique à vontade para experimentar e ajustar o código para se adequar às suas necessidades.

Até a próxima e bons estudos! 👨🏽‍🎓 🚀

“Aprender é a única coisa de que a mente nunca se cansa, nunca tem medo e nunca se arrepende.” — Autor Desconhecido

--

--

Edson Santos

Em Fevereiro de 2022, iniciei uma jornada para adquirir e comparilhar todo o conhecimento aprofundado em criar aplicativos móveis com Swift e SwiftUI.