Networking & Resilience
URLSession, retry com backoff, circuit breaker e cert pinning
Em apps iOS, chamadas de rede falham por inúmeras razões: conexão instável, timeout, servidor sobrecarregado. Retry com backoff exponencial e circuit breaker são essenciais para uma experiência resiliente.
Estratégias de Retry
Quando uma requisição falha com erro transitório, podemos repetir após um intervalo crescente. A chave é distinguir erros retryable de erros permanentes.
| Estratégia | Fórmula | Quando usar |
|---|---|---|
| Constant | delay = base | Rate limiting (429) com Retry-After header |
| Linear | delay = attempt * base | Degradação previsível e controlada |
| Exponential | delay = base * 2^attempt | Falhas transitórias genéricas |
| Exponential + Jitter | (capped + random) / 2 | Recomendado — evita thundering herd |
Status Codes Retryable
| Status Code | Significado | Ação |
|---|---|---|
| 408 | Request Timeout | Retry — servidor não respondeu a tempo |
| 429 | Too Many Requests | Retry após Retry-After header |
| 500 | Internal Server Error | Retry — pode ser transitório |
| 502 | Bad Gateway | Retry — proxy/load balancer issue |
| 503 | Service Unavailable | Retry — serviço temporariamente indisponível |
| 504 | Gateway Timeout | Retry — upstream não respondeu |
RetryPolicy do IdentityKit
// RetryPolicy — IdentityKit
public struct RetryPolicy: Sendable {
public let maxAttempts: Int
public let baseDelay: TimeInterval
public let maxDelay: TimeInterval
public func delay(forAttempt attempt: Int) -> TimeInterval {
let exponential = baseDelay * pow(2.0, Double(attempt))
let capped = min(exponential, maxDelay)
let jitter = Double.random(in: 0...capped)
return (capped + jitter) / 2.0
}
public func isRetryable(statusCode: Int) -> Bool {
switch statusCode {
case 408, 429, 500, 502, 503, 504: return true
default: return false
}
}
public static let `default` = RetryPolicy(
maxAttempts: 3,
baseDelay: 1.0,
maxDelay: 30.0
)
}
Por que usar
(capped + jitter) / 2 em vez de apenas capped + jitter? A fórmula
(capped + jitter) / 2 garante que o delay médio fique próximo ao valor exponencial esperado, mas com variação aleatória. Se usássemos apenas soma, o delay médio seria 1.5x o esperado, desperdiçando tempo. A divisão mantém o comportamento exponencial enquanto distribui os retries no tempo.