Proxies in Swift verwenden: URLSession Guide für iOS und macOS

Ein code-first Guide zum Konfigurieren von HTTP- und SOCKS5-Proxys in Swift mit URLSession. Covers connectionProxyDictionary, Authentifizierung, Geo-Targeting, async/await und Produktionstipps.

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

Wenn Sie Proxies in Swift verwenden möchten, stehen Sie vor einer besonderen Herausforderung: Die URLSession-API von Apple bietet keine eingebaute Proxy-Verwaltung wie Python oder Node.js. Stattdessen müssen Sie URLSessionConfiguration.connectionProxyDictionary manuell konfigurieren und Authentifizierungs-Header selbst setzen. Dieser Guide zeigt Ihnen, wie Sie HTTP- und SOCKS5-Proxys in iOS- und macOS-Apps integrieren — mit runnable Code-Beispielen, Geo-Targeting, async/await und Produktionstipps für Swift Web Scraping.

Proxies in Swift verwenden: Grundlagen und URLSession

Ein Swift Proxy leitet HTTP- oder SOCKS5-Traffic über einen Zwischenserver, bevor er das Ziel erreicht. Auf iOS und macOS erfolgt dies über URLSession, Apples primäres Networking-Framework. Die Konfiguration läuft über URLSessionConfiguration, das ein connectionProxyDictionary akzeptiert — ein Dictionary mit CFNetwork-Konstanten als Schlüsseln.

Die Herausforderung: Apple unterstützt keine direkte Proxy-Authentifizierung über kCFProxyUsernameKey und kCFProxyPasswordKey in URLSession. Diese Schlüssel werden auf Plattformen wie macOS teilweise ignoriert. Die zuverlässige Lösung ist ein Proxy-Authorization-Header oder die Implementierung des urlSession(_:didReceive:completionHandler:)-Delegaten für 407-Herausforderungen.

Residential Proxys sind besonders wichtig, wenn App-Endpunkte Datacenter-IPs blockieren oder inhaltsabhängige Geo-Beschränkungen durchsetzen. Ein URLSession Proxy mit residentialen IPs tarnt Ihre Anfragen als reguläre Endbenutzer-Verbindungen und umgeht einfache IP-basierte Sperren. Weitere Hintergrundinformationen finden Sie in der offiziellen URLSession-Dokumentation von Apple.

HTTP-Proxy mit URLSessionConfiguration konfigurieren

Der erste Schritt ist die Konfiguration eines HTTP/HTTPS-Proxys über connectionProxyDictionary. Sie setzen kCFNetworkProxiesHTTPEnable auf true und geben Host sowie Port an. Für HTTPS-Verbindungen verwenden Sie die kCFNetworkProxiesHTTPSEnable-Familie. Der Standard-Port für ProxyHat HTTP ist 8080.

Grundlegende Proxy-Konfiguration

import Foundation

func createProxySession() -> URLSession {
    let config = URLSessionConfiguration.default
    config.connectionProxyDictionary = [
        kCFNetworkProxiesHTTPEnable: true,
        kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
        kCFNetworkProxiesHTTPPort: 8080,
        kCFNetworkProxiesHTTPSEnable: true,
        kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
        kCFNetworkProxiesHTTPSPort: 8080
    ]
    return URLSession(configuration: config)
}

// Basis-Nutzung ohne Authentifizierung
let session = createProxySession()
let url = URL(string: "https://httpbin.org/ip")!
let task = session.dataTask(with: url) { data, response, error in
    if let data = data {
        print(String(data: data, encoding: .utf8) ?? "Keine Daten")
    }
}
task.resume()

Authentifizierung und Geo-Targeting

Da kCFProxyUsernameKey und kCFProxyPasswordKey in URLSession unzuverlässig sind, gibt es zwei Ansätze: einen Proxy-Authorization: Basic-Header manuell setzen oder den URLSessionDelegate für 407-Challenges implementieren. Bei ProxyHat kodieren Sie Geo-Targeting und Session-IDs direkt im Benutzernamen — z. B. user-country-US-city-newyork-session-abc123.

Ansatz 1: Proxy-Authorization-Header

import Foundation

func makeProxiedRequest(targetURL: URL) async throws -> Data {
    let config = URLSessionConfiguration.default
    config.connectionProxyDictionary = [
        kCFNetworkProxiesHTTPEnable: true,
        kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
        kCFNetworkProxiesHTTPPort: 8080,
        kCFNetworkProxiesHTTPSEnable: true,
        kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
        kCFNetworkProxiesHTTPSPort: 8080
    ]

    // Benutzername mit Geo-Targeting und Session-ID
    let username = "user-country-US-city-newyork-session-abc123"
    let password = "pass"
    let token = "\(username):\(password)"
    let encoded = Data(token.utf8).base64EncodedString()

    var request = URLRequest(url: targetURL)
    request.setValue("Basic \(encoded)", forHTTPHeaderField: "Proxy-Authorization")

    let session = URLSession(configuration: config)
    let (data, response) = try await session.data(for: request)

    guard let httpResp = response as? HTTPURLResponse,
          (200...299).contains(httpResp.statusCode) else {
        throw URLError(.badServerResponse)
    }
    return data
}

Ansatz 2: URLSessionDelegate für 407-Challenge

import Foundation

final class ProxyAuthDelegate: NSObject, URLSessionDelegate {
    let username: String
    let password: String

    init(username: String, password: String) {
        self.username = username
        self.password = password
    }

    func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {
        switch challenge.protectionSpace.authenticationMethod {
        case NSURLAuthenticationMethodHTTPProxy,
             NSURLAuthenticationMethodHTTPSProxy:
            let cred = URLCredential(
                user: username,
                password: password,
                persistence: .forSession
            )
            completionHandler(.useCredential, cred)
        default:
            completionHandler(.performDefaultHandling, nil)
        }
    }
}

// Nutzung mit Geo-Targeting im Benutzernamen
let delegate = ProxyAuthDelegate(
    username: "user-country-DE-city-berlin-session-xyz789",
    password: "pass"
)
let config = URLSessionConfiguration.default
config.connectionProxyDictionary = [
    kCFNetworkProxiesHTTPEnable: true,
    kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
    kCFNetworkProxiesHTTPPort: 8080,
    kCFNetworkProxiesHTTPSEnable: true,
    kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
    kCFNetworkProxiesHTTPSPort: 8080
]
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)

SOCKS5-Proxy in Swift verwenden

Für SOCKS5-Verbindungen über Port 1080 verwenden Sie die kCFStreamPropertySOCKSProxy*-Schlüssel. SOCKS5 ist nützlich, wenn Sie TCP-Traffic jenseits von HTTP/HTTPS tunneln müssen oder wenn bestimmte Endpunkte HTTP-Proxys blockieren. Der ProxyHat-SOCKS5-Gateway läuft auf derselben Adresse gate.proxyhat.com, nur auf einem anderen Port.

import Foundation

func createSOCKS5Session() -> URLSession {
    let config = URLSessionConfiguration.default
    config.connectionProxyDictionary = [
        kCFStreamPropertySOCKSProxyHostKey: "gate.proxyhat.com",
        kCFStreamPropertySOCKSProxyPortKey: 1080,
        kCFStreamPropertySOCKSProxyVersionKey: kCFStreamSocketSOCKSVersion5
    ]
    // Auch hier: Proxy-Authorization-Header oder Delegate verwenden
    return URLSession(configuration: config)
}

Async/Await mit Proxys: Web Scraping in Swift

Swift Web Scraping mit async/await und Proxys ist mit URLSession.shared.data(for:) unkompliziert. Für parallele Anfragen an mehrere Endpunkte verwenden Sie eine TaskGroup. Das folgende Beispiel zeigt eine iOS Proxy URLSession-Implementierung, die JSON-Daten von mehreren URLs concurrently abruft und mit Codable dekodiert. Mit residentialen Proxys liegt die typische End-to-End-Latenz bei 200–500ms pro Request.

import Foundation

struct PriceResult: Decodable {
    let product: String
    let price: Double
    let currency: String
}

func makeProxiedSession(country: String, city: String, sessionID: String) -> URLSession {
    let config = URLSessionConfiguration.default
    config.connectionProxyDictionary = [
        kCFNetworkProxiesHTTPEnable: true,
        kCFNetworkProxiesHTTPProxy: "gate.proxyhat.com",
        kCFNetworkProxiesHTTPPort: 8080,
        kCFNetworkProxiesHTTPSEnable: true,
        kCFNetworkProxiesHTTPSProxy: "gate.proxyhat.com",
        kCFNetworkProxiesHTTPSPort: 8080
    ]
    config.timeoutIntervalForRequest = 30
    config.timeoutIntervalForResource = 60
    return URLSession(configuration: config)
}

func fetchPrices(urls: [URL], country: String) async throws -> [PriceResult] {
    let session = makeProxiedSession(
        country: country,
        city: "berlin",
        sessionID: "batch-\(UUID().uuidString.prefix(8))"
    )

    return try await withThrowingTaskGroup(of: PriceResult?.self) { group in
        for url in urls {
            group.addTask {
                let username = "user-country-\(country)-session-\(UUID().uuidString.prefix(8))"
                let token = Data("\(username):pass".utf8).base64EncodedString()

                var request = URLRequest(url: url)
                request.setValue("Basic \(token)", forHTTPHeaderField: "Proxy-Authorization")
                request.setValue("application/json", forHTTPHeaderField: "Accept")

                do {
                    let (data, response) = try await session.data(for: request)
                    guard let http = response as? HTTPURLResponse,
                          (200...299).contains(http.statusCode) else {
                        return nil
                    }
                    return try JSONDecoder().decode(PriceResult.self, from: data)
                } catch {
                    print("Fehler bei \(url): \(error)")
                    return nil
                }
            }
        }

        var results: [PriceResult] = []
        for try await result in group {
            if let result = result {
                results.append(result)
            }
        }
        return results
    }
}

let urls = [
    URL(string: "https://api.shop.example/prices/1")!,
    URL(string: "https://api.shop.example/prices/2")!,
    URL(string: "https://api.shop.example/prices/3")!
]

Task {
    do {
        let prices = try await fetchPrices(urls: urls, country: "DE")
        for p in prices {
            print("\(p.product): \(p.price) \(p.currency)")
        }
    } catch {
        print("Batch-Fehler: \(error)")
    }
}

Produktionstipps: TLS, Retry und ATS

URLSessionDelegate für TLS-Validierung

In Produktion müssen Sie TLS-Zertifikate korrekt validieren. Ein URLSessionDelegate, der urlSession(_:didReceive:completionHandler:) für Server-Trust implementiert, stellt sicher, dass die Verbindung zum Proxy die Identität des Ziels nicht kompromittiert. Bei HTTPS über einen HTTP-Proxy bleibt die TLS-Verbindung Ende-zu-Ende zwischen Client und Zielserver — der Proxy sieht nur den CONNECT-Tunnel.

import Foundation

final class SecureProxyDelegate: NSObject, URLSessionDelegate {
    func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
            completionHandler(.useCredential, credential)
        } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPProxy {
            let cred = URLCredential(
                user: "user-country-US-session-\(UUID().uuidString.prefix(8))",
                password: "pass",
                persistence: .forSession
            )
            completionHandler(.useCredential, cred)
        } else {
            completionHandler(.performDefaultHandling, nil)
        }
    }
}

Retry mit exponentiellem Backoff

Bei 429- oder 503-Antworten sollten Sie mit exponentiellem Backoff erneut versuchen. Eine einfache Implementierung mit async/await und maximal 5 Versuchen:

import Foundation

enum ProxyError: Error {
    case maxRetriesExceeded
    case httpError(Int)
}

func fetchWithRetry(
    url: URL,
    session: URLSession,
    maxRetries: Int = 5,
    baseDelay: TimeInterval = 1.0
) async throws -> Data {
    var attempt = 0
    while attempt < maxRetries {
        let delay = baseDelay * pow(2.0, Double(attempt))
        if attempt > 0 {
            try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
        }

        var request = URLRequest(url: url)
        let token = Data("user-country-DE-session-retry-\(attempt):pass".utf8).base64EncodedString()
        request.setValue("Basic \(token)", forHTTPHeaderField: "Proxy-Authorization")

        do {
            let (data, response) = try await session.data(for: request)
            if let http = response as? HTTPURLResponse {
                if (200...299).contains(http.statusCode) {
                    return data
                }
                if http.statusCode == 429 || http.statusCode == 503 {
                    attempt += 1
                    continue
                }
                throw ProxyError.httpError(http.statusCode)
            }
        } catch {
            attempt += 1
            if attempt >= maxRetries {
                throw ProxyError.maxRetriesExceeded
            }
        }
    }
    throw ProxyError.maxRetriesExceeded
}

App Transport Security (ATS)

Apples ATS erfordert TLS 1.2 oder höher für alle Verbindungen. Wenn Ihr Proxy HTTP-CONNECT-Tunnel für HTTPS verwendet, bleibt die TLS-Verbindung Ende-zu-Ende zwischen Client und Zielserver — der Proxy sieht nur den Tunnel. Sie sollten keine ATS-Ausnahmen (NSAllowsArbitraryLoads) für Proxy-Verbindungen benötigen. Weitere Details finden Sie in den ATS-Dokumentationsrichtlinien von Apple.

Proxy-Typen im Vergleich

Eigenschaft HTTP/HTTPS Proxy (Port 8080) SOCKS5 Proxy (Port 1080)
Protokoll-Support HTTP, HTTPS (via CONNECT) TCP, beliebige Protokolle
Authentifizierung Proxy-Authorization-Header oder 407-Challenge Username/Password im SOCKS-Handshake
URLSession-Support Vollständig über connectionProxyDictionary Über kCFStreamPropertySOCKSProxy*
Geo-Targeting Im Benutzernamen kodiert Im Benutzernamen kodiert
Latenz-Overhead Niedrig (~20–50ms) Minimal (~10–30ms)
Typischer Use Case Web Scraping, API-Aufrufe TCP-Tunneling, nicht-HTTP-Protokolle

ProxyHat-spezifische Einrichtung

ProxyHat verwendet einen einfachen Gateway-Ansatz: Verbinden Sie sich mit gate.proxyhat.com auf Port 8080 (HTTP) oder 1080 (SOCKS5). Die Authentifizierung erfolgt über den Benutzernamen, der Geo-Targeting und Session-IDs kodieren kann. Die vollständige Dokumentation finden Sie unter docs.proxyhat.com.

Der ProxyHat-Ansatz ist sprachunabhängig: Das Web-Scraping-Use-Case funktioniert gleichermaßen mit Swift, Python oder Node.js. Für SERP-Tracking können Sie die gleiche Gateway-Adresse mit unterschiedlichen Session-IDs verwenden. Eine Übersicht aller verfügbaren Proxy-Standorte hilft bei der Auswahl der richtigen Geo-Targeting-Parameter. Aktuelle Preise und Pakete finden Sie auf der ProxyHat-Preisseite.

Beachten Sie, dass ProxyHat auch SDKs für Python und Node.js anbietet, die denselben Gateway verwenden. Die in Swift gezeigten Konzepte — connectionProxyDictionary, Proxy-Authorization-Header, Geo-Targeting im Benutzernamen — lassen sich direkt auf andere Sprachen übertragen.

Ethik, Recht und App Store Richtlinien

Beim Swift Web Scraping mit Proxys sollten Sie rechtliche und ethische Grenzen beachten:

  • Legitime öffentliche Daten: Scrapen Sie nur öffentlich verfügbare Daten, für die Sie keine Nutzungsbedingungen verletzen.
  • CFAA (USA): Der Computer Fraud and Abuse Act kann unbefugten Zugriff auf geschützte Systeme strafbar machen — selbst bei öffentlichen Daten, wenn ToS dies untersagen.
  • GDPR (EU): Personenbezogene Daten unterliegen der DSGVO. Verarbeiten Sie keine personenbezogenen Daten ohne Rechtsgrundlage.
  • App Store Richtlinien: Apples App Store Review Guidelines verbieten Apps, die Inhalte ohne Zustimmung des Eigentümers scrapen. Bevorzugen Sie offizielle APIs, wenn verfügbar.
  • robots.txt: Respektieren Sie robots.txt-Anweisungen der Ziel-Websites.

Best Practice: Bevorzugen Sie immer offizielle APIs. Wenn eine API verfügbar ist, ist Scraping über Proxys unnötig und potenziell rechtlich riskant. Proxys sind ein Werkzeug für legitime Datensammlung, nicht für Umgehung von Zugriffsbeschränkungen.

Key Takeaways

  • URLSessionConfiguration.connectionProxyDictionary ist der primäre Weg, Proxys in Swift zu konfigurieren — mit kCFNetworkProxiesHTTP* für HTTP und kCFStreamPropertySOCKSProxy* für SOCKS5.
  • kCFProxyUsernameKey und kCFProxyPasswordKey sind in URLSession unzuverlässig — verwenden Sie Proxy-Authorization-Header oder URLSessionDelegate.
  • Geo-Targeting und Session-IDs werden im Benutzernamen kodiert: user-country-US-city-newyork-session-abc123.
  • async/await mit TaskGroup ermöglicht parallele, proxy-basierte Requests mit typischer Latenz von 200–500ms pro Anfrage.
  • ATS bleibt aktiv — Proxys tunneln HTTPS über CONNECT ohne ATS-Ausnahmen.
  • Residential Proxys umgehen Datacenter-IP-Sperren, erfordern aber ethische und rechtliche Sorgfalt gemäß CFAA, GDPR und App Store Guidelines.

Bereit loszulegen?

Zugang zu über 50 Mio. Residential-IPs in über 148 Ländern mit KI-gesteuerter Filterung.

Preise ansehenResidential Proxies
← Zurück zum Blog