Network Framework Migration Guides Migration 1: From BSD Sockets to NWConnection Migration mapping BSD Sockets NWConnection Notes socket() + connect() NWConnection(host:port:using:) + start() Non-blocking by default send() / sendto() connection.send(content:completion:) Async, returns immediately recv() / recvfrom() connection.receive(minimumIncompleteLength:maximumLength:completion:) Async, returns immediately bind() + listen() NWListener(using:on:) Automatic port binding accept() listener.newConnectionHandler Callback for each connection getaddrinfo() Let NWConnection handle DNS Smart resolution with racing SCNetworkReachability connection.stateUpdateHandler waiting state No race conditions setsockopt() NWParameters configuration Type-safe options Example migration Before (BSD Sockets) // BEFORE — Blocking, manual DNS, error-prone var hints = addrinfo() hints.ai_family = AF_INET hints.ai_socktype = SOCK_STREAM
var results: UnsafeMutablePointer
let sock = socket(results.pointee.ai_family, results.pointee.ai_socktype, 0) connect(sock, results.pointee.ai_addr, results.pointee.ai_addrlen) // BLOCKS
let data = "Hello".data(using: .utf8)! data.withUnsafeBytes { ptr in send(sock, ptr.baseAddress, data.count, 0) }
After (NWConnection) // AFTER — Non-blocking, automatic DNS, type-safe let connection = NWConnection( host: NWEndpoint.Host("example.com"), port: NWEndpoint.Port(integerLiteral: 443), using: .tls )
connection.stateUpdateHandler = { state in if case .ready = state { let data = Data("Hello".utf8) connection.send(content: data, completion: .contentProcessed { error in if let error = error { print("Send failed: (error)") } }) } }
connection.start(queue: .main)
Benefits 20 lines → 10 lines No manual DNS, no blocking, no unsafe pointers Automatic Happy Eyeballs, proxy support, WiFi Assist Migration 2: From NWConnection to NetworkConnection (iOS 26+) Why migrate Async/await eliminates callback hell TLV framing and Coder protocol built-in No [weak self] needed (async/await handles cancellation) State monitoring via async sequences Migration mapping NWConnection (iOS 12-25) NetworkConnection (iOS 26+) Notes connection.stateUpdateHandler = { state in } for await state in connection.states { } Async sequence connection.send(content:completion:) try await connection.send(content) Suspending function connection.receive(minimumIncompleteLength:maximumLength:completion:) try await connection.receive(exactly:) Suspending function Manual JSON encode/decode Coder(MyType.self, using: .json) Built-in Codable support Custom framer TLV { TLS() } Built-in Type-Length-Value [weak self] everywhere No [weak self] needed Task cancellation automatic Example migration Before (NWConnection) // BEFORE — Completion handlers, manual memory management let connection = NWConnection(host: "example.com", port: 443, using: .tls)
connection.stateUpdateHandler = { [weak self] state in switch state { case .ready: self?.sendData() case .waiting(let error): print("Waiting: (error)") case .failed(let error): print("Failed: (error)") default: break } }
connection.start(queue: .main)
func sendData() { let data = Data("Hello".utf8) connection.send(content: data, completion: .contentProcessed { [weak self] error in if let error = error { print("Send error: (error)") return } self?.receiveData() }) }
func receiveData() { connection.receive(minimumIncompleteLength: 10, maximumLength: 10) { [weak self] (data, context, isComplete, error) in if let error = error { print("Receive error: (error)") return } if let data = data { print("Received: (data)") } } }
After (NetworkConnection) // AFTER — Async/await, automatic memory management let connection = NetworkConnection( to: .hostPort(host: "example.com", port: 443) ) { TLS() }
// Monitor states in background task Task { for await state in connection.states { switch state { case .preparing: print("Connecting...") case .ready: print("Ready") case .waiting(let error): print("Waiting: (error)") case .failed(let error): print("Failed: (error)") default: break } } }
// Send and receive with async/await func sendAndReceive() async throws { let data = Data("Hello".utf8) try await connection.send(data)
let received = try await connection.receive(exactly: 10).content
print("Received: \(received)")
}
Benefits 30 lines → 15 lines No callback nesting, no [weak self] Errors propagate naturally with throws Automatic cancellation on Task exit Migration 3: From URLSession StreamTask to NetworkConnection When to migrate Need UDP (StreamTask only supports TCP) Need custom protocols beyond TCP/TLS Need low-level control (packet pacing, ECN, service class) When to STAY with URLSession Doing HTTP/HTTPS (URLSession optimized for this) Need WebSocket support Need built-in caching, cookie handling Example migration Before (URLSession StreamTask) // BEFORE — URLSession for TCP/TLS stream let task = URLSession.shared.streamTask(withHostName: "example.com", port: 443)
task.resume()
task.write(Data("Hello".utf8), timeout: 10) { error in if let error = error { print("Write error: (error)") } }
task.readData(ofMinLength: 10, maxLength: 10, timeout: 10) { data, atEOF, error in if let error = error { print("Read error: (error)") return } if let data = data { print("Received: (data)") } }
After (NetworkConnection) // AFTER — NetworkConnection for TCP/TLS let connection = NetworkConnection( to: .hostPort(host: "example.com", port: 443) ) { TLS() }
func sendAndReceive() async throws { try await connection.send(Data("Hello".utf8)) let data = try await connection.receive(exactly: 10).content print("Received: (data)") }
Resources
Skills: axiom-ios-networking, axiom-networking-legacy