Introdução: de GETs básicos ao cliente HTTP industrial
Uma requisição HTTP padrão em Swift são três linhas de código com URLSession.shared.dataTask. Mas em projetos reais (download de arquivos, autenticação com tokens, cache, retentativas) isso não é suficiente. Neste artigo, vamos explorar técnicas avançadas de criação de cliente HTTP em Swift para iOS e macOS: protocolos personalizados, gerenciamento de sessões, multithreading com async/await e Combine, além de tratamento robusto de erros. O material é voltado para desenvolvedores que já conhecem os fundamentos do URLSession.
Arquitetura de um cliente HTTP avançado
Um cliente industrial não é apenas uma função de requisição, mas uma camada de abstração. Vamos dividi-lo em três níveis:
- Transport Layer — trabalho de baixo nível com URLSession, gerenciamento de configuração.
- Service Layer — serialização/desserialização, tratamento de códigos de status, retentativas.
- Application Layer — lógica de negócios, cache, autenticação.
Começaremos pela personalização da Transport Layer com URLProtocol.
1. URLProtocol personalizado para interceptar e modificar requisições
URLProtocol é um mecanismo poderoso para interceptar todas as requisições de rede no aplicativo. Com ele, é possível adicionar logging, assinatura de requisições ou dados mock para testes.
import Foundation
class LoggingProtocol: URLProtocol { override class func canInit(with request: URLRequest) -> Bool { // Intercepta todas as requisições HTTP/HTTPS return request.url?.scheme == "http" || request.url?.scheme == "https" }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request }
override func startLoading() { print("📤 Requisição: \\(request.httpMethod ?? "GET") \\(request.url?.absoluteString ?? "")") print("📦 Cabeçalhos: \\(request.allHTTPHeaderFields ?? [:])")
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in guard let self = self else { return } if let error = error { print("❌ Erro: \\(error.localizedDescription)") self.client?.urlProtocol(self, didFailWithError: error) } else if let data = data, let response = response { print("✅ Resposta: \\(response.expectedContentLength) bytes") self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed) self.client?.urlProtocol(self, didLoad: data) } self.client?.urlProtocolDidFinishLoading(self) } task.resume() }
override func stopLoading() { // Não faz nada, a tarefa é concluída sozinha }}
// Registro do protocoloURLProtocol.registerClass(LoggingProtocol.self)let config = URLSessionConfiguration.defaultconfig.protocolClasses = [LoggingProtocol.self]let session = URLSession(configuration: config)Agora todas as requisições através desta sessão serão registradas. Da mesma forma, é possível adicionar a inserção automática do token Bearer ou o tratamento do status 401.
2. Gerenciamento de sessões: configuração personalizada e multithreading
Use diferentes URLSessionConfiguration para diferentes finalidades: .default para requisições comuns, .ephemeral para dados privados (sem cache e cookies), .background para downloads em segundo plano.
let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.pythonlib.background")backgroundConfig.isDiscretionary = truebackgroundConfig.sessionSendsLaunchEvents = truelet backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)Para multithreading, use async/await (Swift 5.5+):
func performRequest(url: URL) async throws -> Data { let (data, response)