Introdução: Por que um Cliente HTTP Adequado é Importante para seu App Swift
Apps modernos de iOS e macOS raramente funcionam sem interagir com servidores remotos. Seja carregando dados de uma API REST, enviando análises ou trabalhando com serviços em nuvem — um cliente HTTP em Swift é a base da comunicação em rede. No entanto, muitos desenvolvedores, especialmente iniciantes, cometem erros típicos: ignorar tratamento de erros, não usar recursos modernos da linguagem (async/await) ou criar "código espaguete" com chamadas de rede. Neste artigo, vamos detalhar como escrever um cliente HTTP confiável e eficiente em Swift, seguindo as melhores práticas.
1. Fundamentos do URLSession: O Coração das Requisições de Rede
Na biblioteca padrão do Swift, o framework Foundation e a classe URLSession gerenciam a comunicação em rede. Esta é uma ferramenta poderosa que suporta configuração de sessão, cache, requisições em segundo plano e muito mais.
1.1. Criando um Cliente HTTP Básico
Vamos começar com uma estrutura simples que realizará requisições GET. Usaremos um enum para descrever possíveis erros e um protocolo para flexibilidade:
import Foundation
enum HTTPClientError: Error { case invalidURL case noData case decodingError(Error) case networkError(Error) case statusCode(Int)}
protocol HTTPClientProtocol { func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T}
final class HTTPClient: HTTPClientProtocol { private let session: URLSession private let decoder: JSONDecoder init(session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) { self.session = session self.decoder = decoder } func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T { guard let url = URL(string: urlString) else { throw HTTPClientError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw HTTPClientError.networkError(NSError(domain: "Invalid response", code: -1)) } guard (200...299).contains(httpResponse.statusCode) else { throw HTTPClientError.statusCode(httpResponse.statusCode) } do { return try decoder.decode(T.self, from: data) } catch { throw HTTPClientError.decodingError(error) } }}1.2. Por que async/await?
Antes do Swift 5.5, os desenvolvedores usavam closures (handlers de conclusão) ou bibliotecas de terceiros (Alamofire). Async/await torna o código linear, claro e fácil de ler. Erros são tratados através de do-catch em vez de closures aninhadas.
2. POST, PUT, DELETE: Trabalhando com Diferentes Métodos HTTP
Um cliente HTTP real deve suportar todos os métodos principais. Vamos adicionar métodos ao protocolo para enviar dados e atualizar recursos.
2.1. Estendendo o Protocolo para POST e PUT
extension HTTPClientProtocol { func post<T: Decodable, U: Encodable>( to urlString: String, body: U, responseType: T.Type ) async throws -> T { guard let url = URL(string: urlString) else { throw HTTPClientError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") request.httpBody = try JSONEncoder().encode(body) return try a