Desenvolvedores iOS e macOS frequentemente precisam rotear requisições de rede através de um Swift proxy — seja para acessar endpoints que bloqueiam IPs de datacenter, contornar restrições geográficas ou realizar Swift web scraping de dados públicos. Este guia mostra, com código executável, como usar proxies em Swift com URLSession, configurando connectionProxyDictionary, lidando com autenticação, geo-targeting, SOCKS5 e padrões modernos de concorrência com async/await.
Por que usar proxies em Swift com URLSession
O URLSession é a API padrão da Apple para networking em iOS e macOS. Por padrão, ele segue as configurações de proxy do sistema, mas em muitos cenários você precisa de controle programático — especialmente ao usar proxies residenciais como o ProxyHat, onde cada IP de saída pertence a um dispositivo real em uma operadora ou ISP.
O problema é real: muitos endpoints de APIs, plataformas de e-commerce e serviços de busca aplicam bloqueio de IP de datacenter. Um estudo da Google Search Central confirma que grandes plataformas diferenciam tráfego por faixa de IP. Se o seu app faz requisições de um IP de datacenter (AWS, GCP, Azure), a resposta pode ser um 403 Forbidden ou um desafio CAPTCHA. Proxies residenciais resolvem isso porque o IP de saída é indistinguível do tráfego de um usuário comum.
Antes de mergulhar no código, compare os tipos de proxy disponíveis:
| Tipo de proxy | IP de saída | Custo relativo | Ideal para | Risco de bloqueio |
|---|---|---|---|---|
| Datacenter | IPs de servidores (AWS, OVH, etc.) | Baixo | APIs públicas, testes internos | Alto em endpoints restritos |
| Residencial | IPs de ISPs reais | Médio | Web scraping, SERP tracking, e-commerce | Baixo |
| Mobile | IPs de operadoras móveis | Alto | Apps móveis, conteúdo geo-bloqueado | Muito baixo |
Para a maioria dos casos de uso em Swift — web scraping, SERP tracking e monitoramento de preços — proxies residenciais oferecem o melhor equilíbrio entre custo e confiabilidade. Confira as opções de planos e as localizações disponíveis.
Configurando proxy HTTP/HTTPS com connectionProxyDictionary
O URLSessionConfiguration expõe a propriedade connectionProxyDictionary, que aceita um dicionário com chaves do CoreFoundation. Para rotear todas as requisições HTTP e HTTPS através do gateway ProxyHat em gate.proxyhat.com:8080, use as constantes kCFNetworkProxiesHTTPEnable, kCFNetworkProxiesHTTPProxy, kCFNetworkProxiesHTTPPort e os equivalentes HTTPS.
Segundo a documentação da Apple, esse dicionário substitui as configurações de proxy do sistema para a sessão criada.
import Foundation
func makeProxySession() -> URLSession {
let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
kCFNetworkProxiesHTTPEnable: true,
kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPPort: 8080,
kCFNetworkProxiesHTTPSEnable: true,
kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPSPort: 8080
]
// Timeout agressivo para evitar requisições penduradas
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60
return URLSession(configuration: config)
}
let session = makeProxySession()
let url = URL(string: "https://httpbin.org/ip")!
let task = session.dataTask(with: url) { data, response, error in
if let error = error {
print("Erro: \(error)")
return
}
if let data = data, let body = String(data: data, encoding: .utf8) {
print("Resposta: \(body)")
}
}
task.resume()
Esse exemplo configura iOS proxy URLSession para rotear tanto HTTP quanto HTTPS pelo mesmo gateway na porta 8080. O IP retornado por httpbin.org/ip será o do proxy, não o do dispositivo.
Autenticação e geo-targeting no proxy
O ProxyHat usa autenticação no nível do proxy (HTTP Proxy-Authorization), e o nome de usuário carrega flags de geo-targeting e sessão. O formato é:
user-country-US-city-newyork-session-abc123
Aqui, country-US define o país, city-newyork define a cidade e session-abc123 mantém o mesmo IP de saída entre requisições (sticky session).
O problema com kCFProxyUsernameKey e kCFProxyPasswordKey
Embora existam as chaves kCFProxyUsernameKey e kCFProxyPasswordKey, elas são não confiáveis no URLSession — a Apple não garante que essas credenciais sejam enviadas no handshake do proxy. A solução é enviar o header Proxy-Authorization: Basic manualmente em cada requisição, ou implementar o delegate urlSession(_:didReceive:completionHandler:) para responder ao desafio 407 Proxy Authentication Required.
Abordagem 1: Header Proxy-Authorization manual
import Foundation
func makeProxyAuthHeader(user: String, pass: String) -> String {
let credentials = "\(user):\(pass)"
let encoded = Data(credentials.utf8).base64EncodedString()
return "Basic \(encoded)"
}
func makeProxiedRequest(urlString: String) -> URLRequest {
var request = URLRequest(url: URL(string: urlString)!, timeoutInterval: 30)
// Geo-targeting + sticky session no username
let username = "user-country-US-city-newyork-session-abc123"
let password = "pass"
request.setValue(
makeProxyAuthHeader(user: username, pass: password),
forHTTPHeaderField: "Proxy-Authorization"
)
return request
}
let request = makeProxiedRequest(urlString: "https://httpbin.org/ip")
let task = session.dataTask(with: request) { data, _, error in
if let data = data, let body = String(data: data, encoding: .utf8) {
print(body) // {"origin": "IP_DO_PROXY_RESIDENCIAL"}
}
}
task.resume()
Abordagem 2: URLSessionDelegate para desafio 407
Se o proxy responder com 407 Proxy Authentication Required, o URLSession invoca o método urlSession(_:didReceive:completionHandler:) do delegate. Você pode interceptar e fornecer credenciais dinamicamente.
import Foundation
final class ProxyAuthDelegate: NSObject, URLSessionDelegate {
let proxyUser: String
let proxyPass: String
init(user: String, pass: String) {
self.proxyUser = user
self.proxyPass = pass
}
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
let method = challenge.protectionSpace.authenticationMethod
// Proxy HTTP e HTTPS
guard method == NSURLAuthenticationMethodHTTPProxy ||
method == NSURLAuthenticationMethodHTTPSProxy else {
completionHandler(.performDefaultHandling, nil)
return
}
// Evita loop infinito se já tentamos
if challenge.previousFailureCount > 2 {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let credential = URLCredential(
user: proxyUser,
password: proxyPass,
persistence: .forSession
)
completionHandler(.useCredential, credential)
}
}
// Uso
let delegate = ProxyAuthDelegate(
user: "user-country-DE-city-berlin-session-sess42",
pass: "pass"
)
let session = URLSession(
configuration: makeProxySession().configuration,
delegate: delegate,
delegateQueue: nil
)
Recomendamos a abordagem do header manual como primária, pois é mais previsível e funciona em todas as versões de iOS. O delegate é útil como fallback ou quando você não controla todos os URLRequests (ex.: bibliotecas de terceiros).
SOCKS5 via kCFStreamPropertySOCKSProxy na porta 1080
Para casos onde o HTTP proxy não é suficiente — por exemplo, tunneling de protocolos não-HTTP ou contornar firewalls mais restritivas — o ProxyHat também oferece SOCKS5 na porta 1080. No URLSession, use as chaves kCFNetworkProxiesSOCKSEnable, kCFNetworkProxiesSOCKSProxy e kCFNetworkProxiesSOCKSPort.
import Foundation
func makeSOCKS5Session() -> URLSession {
let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
kCFNetworkProxiesSOCKSEnable: true,
kCFNetworkProxiesSOCKSProxy: "gate.proxyhat.com",
kCFNetworkProxiesSOCKSPort: 1080
]
config.timeoutIntervalForRequest = 30
return URLSession(configuration: config)
}
// Para SOCKS5, a autenticação também vai no header Proxy-Authorization
// ou via delegate, pois kCFProxyUsernameKey continua não confiável
let socksSession = makeSOCKS5Session()
var request = URLRequest(url: URL(string: "https://httpbin.org/ip")!)
request.setValue(
makeProxyAuthHeader(user: "user-country-US-session-sock99", pass: "pass"),
forHTTPHeaderField: "Proxy-Authorization"
)
let task = socksSession.dataTask(with: request) { data, _, _ in
if let data = data, let body = String(data: data, encoding: .utf8) {
print("SOCKS5 resposta: \(body)")
}
}
task.resume()
SOCKS5 adiciona uma camada extra de encapsulamento e pode ter latência ligeiramente maior (~10–30 ms) comparado ao HTTP proxy, mas é mais robusto em redes com filtragem de tráfego HTTP.
Exemplo prático com async/await, Codable e TaskGroup
Com Swift 5.5+ e iOS 15+/macOS 12+, você pode usar async/await com URLSession.shared.data(for:). O exemplo abaixo busca produtos de uma API, decodifica com Codable e usa TaskGroup para concorrência controlada.
import Foundation
struct Product: Codable, Sendable {
let id: Int
let name: String
let price: Double
let currency: String
}
// Sessão configurada com proxy HTTP
let proxyConfig: URLSessionConfiguration = {
let c = URLSessionConfiguration.ephemeral
c.connectionProxyDictionary = [
kCFNetworkProxiesHTTPEnable: true,
kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPPort: 8080,
kCFNetworkProxiesHTTPSEnable: true,
kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
kCFNetworkProxiesHTTPSPort: 8080
]
c.timeoutIntervalForRequest = 30
return c
}()
let proxySession = URLSession(configuration: proxyConfig)
func fetchProduct(id: Int, geoUser: String) async throws -> Product {
var request = URLRequest(
url: URL(string: "https://api.example.com/products/\(id)")!,
timeoutInterval: 30
)
request.setValue(
makeProxyAuthHeader(user: geoUser, pass: "pass"),
forHTTPHeaderField: "Proxy-Authorization"
)
request.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await proxySession.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200..<300).contains(http.statusCode) else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(Product.self, from: data)
}
// Concorrência com TaskGroup — máximo 10 requisições simultâneas
func fetchAllProducts(ids: [Int]) async throws -> [Product] {
try await withThrowingTaskGroup(of: Product.self) { group in
for id in ids {
// Sessão única por requisição para IP rotativo por request
let sessionUser = "user-country-US-session-prod-\(id)"
group.addTask {
try await fetchProduct(id: id, geoUser: sessionUser)
}
}
var results: [Product] = []
for try await product in group {
results.append(product)
}
return results
}
}
// Uso
Task {
let ids = Array(1...50)
do {
let products = try await fetchAllProducts(ids: ids)
print("Baixados \(products.count) produtos")
} catch {
print("Falha: \(error)")
}
}
Note que cada requisição usa um session-prod-{id} diferente, garantindo que o ProxyHat rotacione o IP para cada produto. Se você quiser manter o mesmo IP para todas as requisições (sticky session), use o mesmo identificador de sessão em todas.
Dicas de produção: TLS, retry e ATS
Retry com backoff exponencial
Em produção, requisições via proxy podem falhar por motivos transitórios: timeout, 502, 503 ou queda de conexão. Implemente retry com backoff exponencial para maximizar a taxa de sucesso.
import Foundation
enum ProxyError: Error {
case maxRetriesExceeded
case httpError(Int)
}
func fetchWithRetry(
request: URLRequest,
session: URLSession,
maxAttempts: Int = 3,
baseDelaySeconds: Double = 2.0
) async throws -> Data {
var attempt = 0
while true {
attempt += 1
do {
let (data, response) = try await session.data(for: request)
if let http = response as? HTTPURLResponse {
if (200..<300).contains(http.statusCode) {
return data
}
// Não retentar em 4xx (exceto 429)
if http.statusCode == 429 || http.statusCode >= 500 {
if attempt >= maxAttempts {
throw ProxyError.httpError(http.statusCode)
}
} else {
throw ProxyError.httpError(http.statusCode)
}
}
} catch {
if attempt >= maxAttempts {
throw error
}
}
// Backoff exponencial: 2s, 4s, 8s...
let delay = baseDelaySeconds * pow(2.0, Double(attempt - 1))
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
}
}
// Uso
Task {
var request = URLRequest(url: URL(string: "https://api.example.com/data")!)
request.setValue(
makeProxyAuthHeader(user: "user-country-US-session-r1", pass: "pass"),
forHTTPHeaderField: "Proxy-Authorization"
)
do {
let data = try await fetchWithRetry(
request: request,
session: proxySession,
maxAttempts: 4
)
print("Sucesso: \(data.count) bytes")
} catch {
print("Falha após retries: \(error)")
}
}
URLSessionDelegate para TLS customizado
Se o endpoint usa certificados auto-assinados ou cadeia incomum, você pode implementar urlSession(_:didReceive:completionHandler:) para validação TLS customizada. Em produção, sempre valide a cadeia de certificados — ignorar TLS é inaceitável fora de desenvolvimento.
import Foundation
final class TLSDelegate: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
if let trust = challenge.protectionSpace.serverTrust {
// Em produção: validar SecTrust contra políticas reais
// Aqui apenas para desenvolvimento — NÃO usar em produção
completionHandler(.useCredential, URLCredential(trust: trust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPProxy ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPSProxy {
let cred = URLCredential(
user: "user-country-US-session-tls1",
password: "pass",
persistence: .forSession
)
completionHandler(.useCredential, cred)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
App Transport Security (ATS)
A App Transport Security da Apple exige TLS 1.2+ e certificados válidos por padrão. Ao usar proxies, o ATS não interfere no tunneling HTTP CONNECT (o proxy é um intermediário, não o destino TLS), mas você deve garantir que o endpoint final tenha TLS adequado. Se precisar exceções, configure NSAppTransportSecurity no Info.plist com restrições mínimas — e apenas para domínios específicos, nunca globalmente.
Concorrência e limites
O URLSession suporta até httpMaximumConnectionsPerHost (padrão: 6 no iOS). Para aumentar a concorrência via proxy, ajuste esse valor, mas cuidado: muitos proxies limitam conexões simultâneas por usuário. Com ProxyHat, recomendamos não exceder 100 sessões concorrentes por conta em planos padrão. Para volumes maiores, consulte a documentação do ProxyHat.
Ética e aspectos legais
Usar proxies em Swift para acessar dados públicos é legítimo, mas há limites:
- Computer Fraud and Abuse Act (CFAA) — nos EUA, acessar sistemas além da autorização concedida pode ser crime. Raspar dados públicos (sem login, sem bypass de autenticação) é geralmente aceito, mas consulte um advogado para casos de borda.
- GDPR — na UE, dados pessoais estão protegidos. Se o scraping coleta dados pessoais, você precisa de base legal. O portal GDPR da Comissão Europeia é a fonte oficial.
- Diretrizes da App Store — a Apple proíbe apps que coletam dados de outros apps sem consentimento (Guideline 5.1.2). Se o seu app faz scraping em segundo plano, revise as políticas antes de submeter.
- robots.txt e ToS — sempre verifique
robots.txte os Termos de Serviço do alvo. Respeite rate limits razoáveis (ex.: 1 requisição a cada 2–5 segundos para sites pequenos).
Prefira APIs oficiais quando disponíveis. Se uma plataforma oferece uma API pública com rate limits adequados, não há razão para scraping via proxy. Use proxies quando não há API, quando a API é restrita por região ou quando você precisa de dados em escala que a API não suporta.
Nota sobre o ProxyHat SDK: O ProxyHat oferece SDKs em Python e Node.js que encapsulam a mesma lógica de gateway (
gate.proxyhat.com:8080). Para Swift, não há SDK nativo — você implementa diretamente comURLSessioneconnectionProxyDictionary, como mostrado acima. A lógica de geo-targeting e sessões é idêntica em todas as linguagens.
Principais pontos
- connectionProxyDictionary é a forma programática de configurar proxy HTTP/HTTPS em
URLSession, usando chaveskCFNetworkProxiesHTTP*ekCFNetworkProxiesHTTPS*apontando paragate.proxyhat.com:8080. - Autenticação via header
Proxy-Authorization: Basicé mais confiável quekCFProxyUsernameKey/kCFProxyPasswordKeyno URLSession. Como alternativa, implementeurlSession(_:didReceive:completionHandler:)para o desafio 407. - Geo-targeting vai no username:
user-country-US-city-newyork-session-abc123. Use sessões diferentes para IP rotativo por requisição, ou a mesma sessão para sticky IP. - SOCKS5 na porta
1080usakCFNetworkProxiesSOCKSEnable,kCFNetworkProxiesSOCKSProxyekCFNetworkProxiesSOCKSPort. - async/await + TaskGroup permitem concorrência controlada com
URLSession.shared.data(for:)e decodificaçãoCodable. - Retry com backoff exponencial (2s, 4s, 8s) maximiza a taxa de sucesso em falhas transitórias.
- ATS não bloqueia proxy tunneling, mas exige TLS 1.2+ no endpoint final. Configure exceções apenas para domínios específicos.
- Ética: respeite
robots.txt, ToS, GDPR e CFAA. Prefira APIs oficiais quando disponíveis.
Pronto para começar? Configure seu primeiro Swift proxy com ProxyHat em minutos — acesse o dashboard e obtenha suas credenciais.






