Usar Proxies em Swift com URLSession: Guia Completo para iOS e macOS

Aprenda a configurar proxies residenciais em Swift usando URLSession: HTTP, SOCKS5, autenticação, geo-targeting, async/await e dicas de produção para iOS e macOS.

Using Proxies in Swift: A Code-First Guide to URLSession, SOCKS5 & Residential IPs
Neste artigo

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.txt e 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 com URLSession e connectionProxyDictionary, 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 chaves kCFNetworkProxiesHTTP* e kCFNetworkProxiesHTTPS* apontando para gate.proxyhat.com:8080.
  • Autenticação via header Proxy-Authorization: Basic é mais confiável que kCFProxyUsernameKey/kCFProxyPasswordKey no URLSession. Como alternativa, implemente urlSession(_: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 1080 usa kCFNetworkProxiesSOCKSEnable, kCFNetworkProxiesSOCKSProxy e kCFNetworkProxiesSOCKSPort.
  • async/await + TaskGroup permitem concorrência controlada com URLSession.shared.data(for:) e decodificação Codable.
  • 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.

Perguntas frequentes

O que é usar proxies em Swift?

Usar proxies em Swift significa rotear requisições de rede do seu app iOS ou macOS através de um servidor intermediário, como gate.proxyhat.com:8080. No URLSession, isso é feito configurando connectionProxyDictionary com chaves kCFNetworkProxiesHTTPEnable e kCFNetworkProxiesHTTPProxy, permitindo que o tráfego saia com um IP diferente do dispositivo.

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

Porque muitos endpoints bloqueiam IPs de datacenter (AWS, GCP, Azure) com 403 ou CAPTCHAs. Proxies residenciais fazem o tráfego parecer de um usuário real em um ISP, contornando bloqueios. Isso é essencial para web scraping, SERP tracking e acesso a conteúdo geo-bloqueado em apps iOS e macOS.

Qual tipo de proxy funciona melhor com Swift e URLSession?

Proxies residenciais são os mais versáteis para Swift, pois oferecem IPs de ISPs reais com baixo risco de bloqueio. Para HTTP/HTTPS, configure connectionProxyDictionary com kCFNetworkProxiesHTTPProxy apontando para gate.proxyhat.com:8080. Para SOCKS5, use kCFNetworkProxiesSOCKSEnable com a porta 1080. Proxies mobile são uma alternativa premium para conteúdo geo-bloqueado restrito.

Como evitar bloqueios ao usar proxies em Swift?

Use proxies residenciais com rotação de IP por requisição (sessions diferentes no username), implemente retry com backoff exponencial (2s, 4s, 8s), respeite robots.txt e rate limits do alvo, e adicione headers realistas como User-Agent e Accept. Evite mais de 100 sessões concorrentes por conta e prefira sticky sessions apenas quando necessário para manter estado.

Pronto para começar?

Proxies residenciais, ISP e móveis em mais de 148 países. Crie uma conta grátis.

Criar conta grátis
← Voltar ao Blog