Usar Proxies em Swift com URLSession: Guia Prático para iOS e macOS

Aprenda a configurar proxies residenciais em Swift usando URLSession, com autenticação, geo-segmentação, SOCKS5, concorrência com async/await e dicas de produção para apps iOS e macOS.

Using Proxies in Swift: A Code-First URLSession Guide for iOS & macOS

Se você desenvolve apps iOS ou macOS e precisa acessar endpoints que bloqueiam IPs de datacenter, impõem limites agressivos por IP ou restringem conteúdo por região, usar proxies em Swift com URLSession é a solução mais nativa e performática. Este guia mostra, com código executável, como configurar URLSessionConfiguration.connectionProxyDictionary, lidar com autenticação e geo-segmentação, usar SOCKS5, orquestrar concorrência com async/await e aplicar práticas de produção.

Por que usar proxies em Swift com URLSession

Endpoints modernos — de APIs de e-commerce a SERPs do Google — empregam anti-bot baseado em reputação de IP. IPs de datacenter (AWS, GCP, DigitalOcean) são frequentemente sinalizados por sistemas como Cloudflare e Akamai, resultando em HTTP 403 ou desafios CAPTCHA. Proxies residenciais usam IPs atribuídos a ISPs reais, o que reduz drasticamente a taxa de bloqueio. Em testes típicos de scraping, proxies residenciais alcançam 90–95% de sucesso contra 30–50% com IPs datacenter em endpoints protegidos.

O URLSession suporta proxies via URLSessionConfiguration.connectionProxyDictionary, que mapeia para as chaves kCFNetworkProxies* do Core Foundation. Isso permite configurar HTTP, HTTPS e SOCKS5 sem bibliotecas de terceiros — ideal para manter o bundle leve e aderente às diretrizes da App Store. Para casos de uso como web scraping e SERP tracking, essa abordagem é suficiente e robusta.

Configuração básica de proxy HTTP/HTTPS

A configuração de proxy no URLSession é feita via um dicionário que define host, porta e habilitação separadamente para HTTP e HTTPS. As chaves kCFNetworkProxiesHTTPEnable, kCFNetworkProxiesHTTPProxy e kCFNetworkProxiesHTTPPort controlam o proxy HTTP; as equivalentes HTTPS controlam o túnel CONNECT para HTTPS.

import Foundation

func makeProxySession(host: String = "gate.proxyhat.com",
                      port: Int = 8080) -> URLSession {
    let config = URLSessionConfiguration.default
    config.connectionProxyDictionary = [
        kCFNetworkProxiesHTTPEnable: true,
        kCFNetworkProxiesHTTPProxy: host,
        kCFNetworkProxiesHTTPPort: port,
        kCFNetworkProxiesHTTPSEnable: true,
        kCFNetworkProxiesHTTPSProxy: host,
        kCFNetworkProxiesHTTPSPort: port
    ] as [String: Any]
    // Timeout agressivo para evitar pendurar requisições
    config.timeoutIntervalForRequest = 30
    config.timeoutIntervalForResource = 60
    return URLSession(configuration: config)
}

Esse snippet cria uma URLSession que encaminha todo tráfego HTTP e HTTPS por gate.proxyhat.com:8080. A porta 8080 é o endpoint HTTP padrão do ProxyHat; para SOCKS5 use 1080.

Autenticação e geo-segmentação

Aqui está o maior obstáculo prático: as chaves kCFProxyUsernameKey e kCFProxyPasswordKey são não confiáveis no URLSession em iOS e macOS. Em vez de depender delas, há duas abordagens robustas:

  1. Injetar um header Proxy-Authorization: Basic ... manualmente em cada URLRequest.
  2. Implementar urlSession(_:didReceive:) no URLSessionDelegate para responder ao desafio 407.

O ProxyHat codifica país, cidade e sessão diretamente no username, no formato user-country-US-city-newyork-session-abc123. Veja como gerar o header de autenticação:

import Foundation

enum ProxyAuth {
    static func basicHeader(user: String, pass: String) -> String {
        let credentials = "\(user):\(pass)"
        let base64 = Data(credentials.utf8).base64EncodedString()
        return "Basic \(base64)"
    }
}

func makeRequest(url: URL, proxyUser: String, proxyPass: String) -> URLRequest {
    var req = URLRequest(url: url)
    req.setValue(
        ProxyAuth.basicHeader(user: proxyUser, pass: proxyPass),
        forHTTPHeaderField: "Proxy-Authorization"
    )
    req.setValue("Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)",
                forHTTPHeaderField: "User-Agent")
    return req
}

// Exemplo: IP residencial em Nova York com sessão fixa
let user = "user-country-US-city-newyork-session-abc123"
let pass = "sua_senha_aqui"
let req = makeRequest(url: URL(string: "https://httpbin.org/ip")!,
                      proxyUser: user, proxyPass: pass)

Como alternativa mais limpa, implemente o delegate para o desafio 407:

import Foundation

final class ProxyChallengeDelegate: 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) {
        guard challenge.protectionSpace.authenticationMethod
                == NSURLAuthenticationMethodHTTPProxy else {
            completionHandler(.performDefaultHandling, nil)
            return
        }
        let cred = URLCredential(user: proxyUser,
                                 password: proxyPass,
                                 persistence: .forSession)
        completionHandler(.useCredential, cred)
    }
}

Essa abordagem é mais idiomática e funciona mesmo quando o proxy exige Proxy-Authorization no handshake CONNECT. Combine o delegate com a URLSession configurada acima.

SOCKS5 na porta 1080

Para casos em que o túnel SOCKS5 é preferível — por exemplo, para encapsular tráfego não-HTTP — use as chaves kCFStreamPropertySOCKSProxy* na porta 1080:

import Foundation

func makeSOCKS5Session() -> URLSession {
    let config = URLSessionConfiguration.default
    config.connectionProxyDictionary = [
        kCFNetworkProxiesSOCKSEnable: true,
        kCFNetworkProxiesSOCKSProxy: "gate.proxyhat.com",
        kCFNetworkProxiesSOCKSPort: 1080
    ] as [String: Any]
    return URLSession(configuration: config)
}

Note que a autenticação SOCKS5 via URLSession também é limitada; a abordagem do header Proxy-Authorization não se aplica a SOCKS5 puro, então o delegate 407 é o caminho recomendado. Em ambientes onde o SOCKS5 autenticado é essencial, considere usar o ProxyHat SDK (Python/Node), que espelha o mesmo gateway gate.proxyhat.com e lida com SOCKS5 de forma transparente.

Por que proxies residenciais para endpoints de apps

Apps iOS frequentemente consomem APIs que aplicam rate limiting por IP e bloqueio geográfico. Por exemplo, serviços de streaming podem restringir catálogos por país, e marketplaces podem servir preços diferentes conforme a região. Proxies residenciais permitem:

  • Contornar bloqueios de datacenter: IPs de ISP real passam por filtros de reputação que rejeitam ranges cloud.
  • Acessar conteúdo region-locked: com user-country-DE, você obtém um IP alemão e vê o catálogo local.
  • Distribuir requisições: rotação por requisição evita exceder limites como 100 requisições/minuto por IP.

Para aprofundar em tipos de proxy e estratégias de rotação, consulte a documentação oficial do URLSessionConfiguration da Apple e o URLSessionDelegate.

Exemplo completo com async/await e TaskGroup

O exemplo a seguir busca dados JSON de múltiplas URLs em paralelo, usando proxies residenciais com geo-segmentação, decodificação Codable e tratamento de erros. Ele demonstra o padrão recomendado para Swift web scraping em produção.

import Foundation

struct IPResponse: Codable {
    let origin: String
}

struct ScrapeResult: Codable {
    let url: String
    let ip: String
    let statusCode: Int
}

actor Scraper {
    let session: URLSession
    let proxyUser: String
    let proxyPass: String

    init(user: String, pass: String) {
        self.proxyUser = user
        self.proxyPass = pass
        let config = URLSessionConfiguration.default
        config.connectionProxyDictionary = [
            kCFNetworkProxiesHTTPEnable: true,
            kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
            kCFNetworkProxiesHTTPPort: 8080,
            kCFNetworkProxiesHTTPSEnable: true,
            kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
            kCFNetworkProxiesHTTPSPort: 8080
        ] as [String: Any]
        config.timeoutIntervalForRequest = 30
        self.session = URLSession(configuration: config)
    }

    func fetchOne(url: URL) async throws -> ScrapeResult {
        var req = URLRequest(url: url)
        let auth = Data("\(proxyUser):\(proxyPass)".utf8)
            .base64EncodedString()
        req.setValue("Basic \(auth)",
                    forHTTPHeaderField: "Proxy-Authorization")
        let (data, response) = try await session.data(for: req)
        guard let http = response as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        let decoded = try JSONDecoder().decode(IPResponse.self, from: data)
        return ScrapeResult(url: url.absoluteString,
                            ip: decoded.origin,
                            statusCode: http.statusCode)
    }

    func fetchAll(urls: [URL], concurrency: Int = 5) async -> [ScrapeResult] {
        await withTaskGroup(of: ScrapeResult?.self) { group in
            var iterator = urls.makeIterator()
            for _ in 0..<concurrency {
                if let url = iterator.next() {
                    group.addTask { try? await self.fetchOne(url: url) }
                }
            }
            var results: [ScrapeResult] = []
            for await result in group {
                if let r = result { results.append(r) }
                if let url = iterator.next() {
                    group.addTask { try? await self.fetchOne(url: url) }
                }
            }
            return results
        }
    }
}

Uso:

let urls = [
    URL(string: "https://httpbin.org/ip")!,
    URL(string: "https://httpbin.org/headers")!,
    URL(string: "https://httpbin.org/user-agent")!
]
let scraper = Scraper(
    user: "user-country-US-city-newyork-session-abc123",
    pass: "sua_senha"
)
Task {
    let results = await scraper.fetchAll(urls: urls, concurrency: 3)
    for r in results { print(r) }
}

Esse padrão limita a concorrência a 5 sessões simultâneas por padrão, evita estourar rate limits e mantém o uso de memória previsível em dispositivos móveis.

Produção: TLS, retries, ATS e privacidade

Delegate TLS e validação de certificado

Em ambientes corporativos ou de QA, pode ser necessário inspecionar o handshake TLS. Implemente urlSession(_:didReceive:completionHandler:) para validação customizada:

import Foundation

final class TLSDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition,
                                                 URLCredential?) -> Void) {
        guard challenge.protectionSpace.authenticationMethod
                == NSURLAuthenticationMethodServerTrust,
              let trust = challenge.protectionSpace.serverTrust else {
            completionHandler(.performDefaultHandling, nil)
            return
        }
        // Em produção, valide contra pinning ou CA custom
        completionHandler(.useCredential, URLCredential(trust: trust))
    }
}

Retry com backoff exponencial

Erros transitórios (timeout, 429, 503) devem ser tratados com retry e backoff exponencial. Evite loops infinitos: limite a 3 tentativas com jitter.

import Foundation

func fetchWithRetry(url: URL, session: URLSession,
                    maxAttempts: Int = 3) async throws -> Data {
    var attempt = 0
    while true {
        attempt += 1
        do {
            let (data, response) = try await session.data(from: url)
            if let http = response as? HTTPURLResponse,
               http.statusCode == 429 || http.statusCode >= 500 {
                throw URLError(.badServerResponse)
            }
            return data
        } catch {
            if attempt >= maxAttempts { throw error }
            let delay = pow(2.0, Double(attempt))
            let jitter = Double.random(in: 0...0.5)
            try await Task.sleep(nanoseconds:
                UInt64((delay + jitter) * 1_000_000_000))
        }
    }
}

App Transport Security (ATS)

O ATS exige TLS 1.2+ e certificados válidos por padrão. Como o proxy ProxyHat opera como MITM transparente apenas no nível de túnel CONNECT (sem quebrar TLS end-to-end), o ATS não precisa ser desativado. Se você precisar conectar a endpoints com certificados self-signed em QA, adicione exceções no Info.plist sob NSAppTransportSecurity — mas nunca em builds de produção.

Privacidade on-device

No iOS 14+, o indicador de privacidade mostra quando seu app usa a rede. Seja transparente com o usuário: declare o uso de rede no NSPrivacyAccessedAPITypes e justifique o uso de proxies no rótulo de privacidade da App Store. Não colete dados pessoais via proxy sem consentimento.

Erros comuns e edge cases

  • 407 Proxy Authentication Required persistente: o header Proxy-Authorization não é enviado no CONNECT. Use o delegate 407.
  • Timeout em HTTPS: confirme que kCFNetworkProxiesHTTPSEnable está true; sem ele, o CONNECT não é roteado.
  • Geo-segmentação ignorada: verifique se o username segue exatamente o formato user-country-US-city-newyork-session-abc123.
  • ATS bloqueando conexões: não desative ATS globalmente; use exceções pontuais.
  • Memory spike em TaskGroup: limite concurrency e processe resultados em streaming quando possível.

Ética e aspectos legais

Usar proxies não isenta você de obrigações legais. Nos EUA, o Computer Fraud and Abuse Act (CFAA) criminaliza acesso não autorizado a sistemas protegidos. Na UE, a GDPR regula o processamento de dados pessoais, incluindo dados coletados via scraping. As diretrizes da App Store da Apple proíbem apps que coletam dados sem consentimento ou violam ToS de terceiros.

Boas práticas:

  • Respeite robots.txt e os Termos de Serviço do alvo.
  • Colete apenas dados públicos e legítimos.
  • Prefira APIs oficiais quando disponíveis.
  • Documente a base legal para processamento de dados pessoais.
  • Não use proxies para evadir bloqueios aplicados por medidas de segurança legítimas.

Configuração no ProxyHat

O ProxyHat expõe o gateway em gate.proxyhat.com nas portas 8080 (HTTP) e 1080 (SOCKS5). A autenticação é via Basic, com flags de geo e sessão no username. Consulte nossos planos e a lista de localizações disponíveis. O ProxyHat SDK (Python/Node) usa o mesmo gateway, então você pode prototipar em Python e migrar para Swift sem mudar credenciais.

ParâmetroHTTPSOCKS5
Hostgate.proxyhat.comgate.proxyhat.com
Porta80801080
AuthBasic (header ou delegate 407)Delegate 407
Geouser-country-US-city-newyorkuser-country-US-city-newyork
Sessão stickyuser-session-abc123user-session-abc123

Key Takeaways

  • connectionProxyDictionary é a forma nativa de configurar proxies em Swift, sem dependências.
  • kCFProxyUsernameKey é não confiável; use header Proxy-Authorization ou delegate 407.
  • Geo-segmentação e sessões sticky vão no username: user-country-US-city-newyork-session-abc123.
  • SOCKS5 usa porta 1080 e chaves kCFStreamPropertySOCKSProxy*.
  • Use async/await com TaskGroup e limite concorrência para evitar rate limits.
  • Respeite CFAA, GDPR, diretrizes da App Store e robots.txt.

FAQ

O que é usar proxies em Swift?

É configurar URLSession via connectionProxyDictionary para encaminhar tráfego HTTP/HTTPS ou SOCKS5 por um servidor intermediário, alterando o IP de saída e permitindo geo-segmentação.

Por que usar proxies em Swift importa para usuários de proxy?

Porque endpoints bloqueiam IPs de datacenter e impõem rate limits por IP. Proxies residenciais reduzem bloqueios e habilitam acesso a conteúdo region-locked, essencial para scraping e QA distribuído.

Qual tipo de proxy funciona melhor para usar proxies em Swift?

Proxies residenciais rotativos com sessões sticky oferecem o melhor equilíbrio. Datacenter é mais rápido mas bloqueado; móvel é mais confiável porém caro. Escolha conforme o caso de uso.

Como evitar bloqueios ao implementar proxies em Swift?

Combine IPs residenciais, rotação por requisição, retries com backoff exponencial, headers realistas, concorrência limitada e geo-segmentação alinhada à região esperada. Prefira APIs oficiais quando existirem.

Pronto para começar?

Acesse mais de 50M de IPs residenciais em mais de 148 países com filtragem por IA.

Ver preçosProxies residenciais
← Voltar ao Blog