Claude Code Plugins

Community-maintained marketplace

Feedback

network-framework-ref

@CharlesWiltgen/Axiom
55
0

Reference — Comprehensive Network.framework guide covering NetworkConnection (iOS 26+), NWConnection (iOS 12-25), TLV framing, Coder protocol, NetworkListener, NetworkBrowser, Wi-Fi Aware discovery, and migration strategies

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name network-framework-ref
description Reference — Comprehensive Network.framework guide covering NetworkConnection (iOS 26+), NWConnection (iOS 12-25), TLV framing, Coder protocol, NetworkListener, NetworkBrowser, Wi-Fi Aware discovery, and migration strategies
version 1.0.0
skill_type reference
apple_platforms iOS 12+ (NWConnection), iOS 26+ (NetworkConnection)
last_updated Tue Dec 02 2025 00:00:00 GMT+0000 (Coordinated Universal Time)

Network.framework API Reference

Overview

Network.framework is Apple's modern networking API that replaces Berkeley sockets, providing smart connection establishment, user-space networking, built-in TLS support, and seamless mobility. Introduced in iOS 12 (2018) with NWConnection and evolved in iOS 26 (2025) with NetworkConnection for structured concurrency.

Evolution timeline

  • 2018 (iOS 12) NWConnection with completion handlers, deprecates CFSocket/NSStream/SCNetworkReachability
  • 2019 (iOS 13) User-space networking (30% CPU reduction), TLS 1.3 default
  • 2025 (iOS 26) NetworkConnection with async/await, TLV framing built-in, Coder protocol, Wi-Fi Aware discovery

Key capabilities

  • Smart connection establishment Happy Eyeballs (IPv4/IPv6 racing), proxy evaluation (PAC), VPN detection, WiFi Assist fallback
  • User-space networking ~30% lower CPU usage vs sockets, memory-mapped regions, reduced context switches
  • Built-in security TLS 1.3 by default, DTLS for UDP, certificate pinning support
  • Mobility Automatic network transition handling (WiFi ↔ cellular), viability notifications, Multipath TCP
  • Performance ECN (Explicit Congestion Notification), service class marking, TCP Fast Open, UDP batching

When to use vs URLSession

  • URLSession HTTP, HTTPS, WebSocket, simple TCP/TLS streams → Use URLSession (optimized for these)
  • Network.framework UDP, custom protocols, low-level control, peer-to-peer, gaming, streaming → Use Network.framework

Related Skills

  • Use networking for anti-patterns, common patterns, pressure scenarios
  • Use networking-diag for systematic troubleshooting of connection failures

When to Use This Skill

Use this skill when:

  • Planning migration from BSD sockets, CFSocket, NSStream, or SCNetworkReachability
  • Understanding API differences between NWConnection (iOS 12-25) and NetworkConnection (iOS 26+)
  • Implementing all 12 WWDC 2025 examples (TLS connection, TLV framing, Coder protocol, NetworkListener, Wi-Fi Aware)
  • Choosing protocols (TCP, UDP, TLS, QUIC) for your use case
  • Peer-to-peer discovery setup with NetworkBrowser and Wi-Fi Aware
  • Optimizing performance with user-space networking, batching, pacing
  • Migrating from completion handlers to async/await (NWConnection → NetworkConnection)

API Evolution

Timeline

Year iOS Version Key Features
2018 iOS 12 NWConnection, NWListener, NWBrowser introduced
2019 iOS 13 User-space networking (30% CPU reduction), TLS 1.3 default
2021 iOS 15 WebSocket support in URLSession
2025 iOS 26 NetworkConnection (async/await), TLV framing, Coder protocol, Wi-Fi Aware

NWConnection (iOS 12-25) vs NetworkConnection (iOS 26+)

Feature NWConnection (iOS 12-25) NetworkConnection (iOS 26+)
Async model Completion handlers async/await structured concurrency
State updates stateUpdateHandler callback states AsyncSequence
Send send(content:completion:) callback try await send(content) suspending
Receive receive(minimumIncompleteLength:maximumLength:completion:) try await receive(exactly:) suspending
Framing Manual or custom NWFramer TLV built-in (TLV { TLS() })
Codable Manual JSON encode/decode Coder protocol (Coder(MyType.self, using: .json))
Memory Requires [weak self] in all closures No [weak self] needed (Task cancellation automatic)
Error handling Check error in completion throws with natural propagation
State machine Callbacks on state changes for await state in connection.states
Discovery NWBrowser (Bonjour only) NetworkBrowser (Bonjour + Wi-Fi Aware)

Recommendation

  • New apps targeting iOS 26+: Use NetworkConnection (cleaner, safer)
  • Apps supporting iOS 12-25: Use NWConnection (backward compatible)
  • Migration: Both APIs coexist, migrate incrementally

NetworkConnection (iOS 26+) Complete Reference

4.1 Creating Connections

NetworkConnection uses declarative protocol stack composition.

Example 1: Basic TLS Connection (WWDC 4:04)

import Network

// Basic connection with TLS (TCP and IP inferred)
let connection = NetworkConnection(
    to: .hostPort(host: "www.example.com", port: 1029)
) {
    TLS()
}

// Send and receive with async/await
public func sendAndReceiveWithTLS() async throws {
    let outgoingData = Data("Hello, world!".utf8)
    try await connection.send(outgoingData)

    let incomingData = try await connection.receive(exactly: 98).content
    print("Received data: \(incomingData)")
}

Key points

  • TLS() infers TCP() and IP() automatically
  • No explicit connection.start() needed (happens on first send/receive)
  • Async/await eliminates callback nesting

Example 2: Custom IP Options (WWDC 4:41)

// Customize IP fragmentation
let connection = NetworkConnection(
    to: .hostPort(host: "www.example.com", port: 1029)
) {
    TLS {
        TCP {
            IP()
                .fragmentationEnabled(false) // Disable IP fragmentation
        }
    }
}

When to customize IP

  • .fragmentationEnabled(false) — For protocols that handle fragmentation themselves (QUIC)
  • .ipVersion(.v6) — Force IPv6 only (testing)

Example 3: Custom Parameters (WWDC 5:07)

// Constrained paths (low data mode) + custom IP
let connection = NetworkConnection(
    to: .hostPort(host: "www.example.com", port: 1029),
    using: .parameters {
        TLS {
            TCP {
                IP()
                    .fragmentationEnabled(false)
            }
        }
    }
    .constrainedPathsProhibited(true) // Don't use cellular in low data mode
)

Common parameters

  • .constrainedPathsProhibited(true) — Respect low data mode
  • .expensivePathsProhibited(true) — Don't use cellular/hotspot
  • .multipathServiceType(.handover) — Enable Multipath TCP

Endpoint Types

// Host + Port
.hostPort(host: "example.com", port: 443)

// Service (Bonjour)
.service(name: "MyPrinter", type: "_ipp._tcp", domain: "local.", interface: nil)

// Unix domain socket
.unix(path: "/tmp/my.sock")

Protocol Stack Composition

// TLS over TCP (most common)
TLS()

// QUIC (TLS + UDP, multiplexed streams)
QUIC()

// UDP (datagrams)
UDP()

// TCP (stream, no encryption)
TCP()

// WebSocket over TLS
WebSocket {
    TLS()
}

// Custom framing
TLV {
    TLS()
}

4.2 State Machine

NetworkConnection transitions through these states:

setup
  ↓
preparing (DNS, TCP handshake, TLS handshake)
  ↓
┌─ waiting (no network, retrying)
│    ↓
└→ ready (can send/receive)
     ↓
  failed (error) or cancelled

Monitoring States

// Option 1: Async sequence (monitor in background)
Task {
    for await state in connection.states {
        switch state {
        case .preparing:
            print("Connecting...")
        case .waiting(let error):
            print("Waiting for network: \(error)")
        case .ready:
            print("Connected!")
        case .failed(let error):
            print("Failed: \(error)")
        case .cancelled:
            print("Cancelled")
        @unknown default:
            break
        }
    }
}

Key states

  • .preparing DNS lookup, TCP SYN, TLS handshake
  • .waiting No network available, framework retries automatically
  • .ready Connection established, can send/receive
  • .failed Unrecoverable error (server refused, TLS failed, timeout)
  • .cancelled Task cancelled or connection.cancel() called

4.3 Send/Receive Patterns

Send: Basic

let data = Data("Hello".utf8)
try await connection.send(data)

Receive: Exact Byte Count (WWDC 7:30)

// Receive exactly 98 bytes
let incomingData = try await connection.receive(exactly: 98).content
print("Received \(incomingData.count) bytes")

Receive: Variable Length (WWDC 8:29)

// Read UInt32 length prefix, then read that many bytes
let remaining32 = try await connection.receive(as: UInt32.self).content
guard var remaining = Int(exactly: remaining32) else { throw MyError.invalidLength }

while remaining > 0 {
    let chunk = try await connection.receive(atLeast: 1, atMost: remaining).content
    remaining -= chunk.count
    // Process chunk...
}

receive() variants

  • receive(exactly: n) — Wait for exactly n bytes
  • receive(atLeast: min, atMost: max) — Get between min and max bytes
  • receive(as: UInt32.self) — Read fixed-size type (network byte order)

4.4 TLV Framing (iOS 26+)

TLV (Type-Length-Value) solves message boundary problem on stream protocols (TCP/TLS).

Format

  • Type: UInt32 (message identifier)
  • Length: UInt32 (message size, automatic)
  • Value: Message bytes

Example: GameMessage with TLV (WWDC 11:06, 11:24, 11:53)

import Network

// Define message types
enum GameMessage: Int {
    case selectedCharacter = 0
    case move = 1
}

struct GameCharacter: Codable {
    let character: String
}

struct GameMove: Codable {
    let row: Int
    let column: Int
}

// Connection with TLV framing
let connection = NetworkConnection(
    to: .hostPort(host: "www.example.com", port: 1029)
) {
    TLV {
        TLS()
    }
}

// Send typed message
public func sendWithTLV() async throws {
    let characterData = try JSONEncoder().encode(GameCharacter(character: "🐨"))
    try await connection.send(characterData, type: GameMessage.selectedCharacter.rawValue)
}

// Receive typed message
public func receiveWithTLV() async throws {
    let (incomingData, metadata) = try await connection.receive()

    switch GameMessage(rawValue: metadata.type) {
    case .selectedCharacter:
        let character = try JSONDecoder().decode(GameCharacter.self, from: incomingData)
        print("Character selected: \(character)")
    case .move:
        let move = try JSONDecoder().decode(GameMove.self, from: incomingData)
        print("Move: row=\(move.row), column=\(move.column)")
    case .none:
        print("Unknown message type: \(metadata.type)")
    }
}

Benefits

  • Message boundaries preserved (send 3 messages → receive exactly 3)
  • Type-safe message handling (enum-based routing)
  • Minimal overhead (8 bytes per message: type + length)

When to use

  • Mixed message types (chat + presence + typing)
  • Existing protocols using TLV
  • Need message boundaries without heavy framing

4.5 Coder Protocol (iOS 26+)

Coder eliminates manual JSON encoding/decoding boilerplate.

Example: GameMessage with Coder (WWDC 12:50, 13:13, 13:53)

import Network

// Define message types as Codable enum
enum GameMessage: Codable {
    case selectedCharacter(String)
    case move(row: Int, column: Int)
}

// Connection with Coder
let connection = NetworkConnection(
    to: .hostPort(host: "www.example.com", port: 1029)
) {
    Coder(GameMessage.self, using: .json) {
        TLS()
    }
}

// Send Codable directly (no encoding needed!)
public func sendWithCoder() async throws {
    let selectedCharacter: GameMessage = .selectedCharacter("🐨")
    try await connection.send(selectedCharacter)
}

// Receive Codable directly (no decoding needed!)
public func receiveWithCoder() async throws {
    let gameMessage = try await connection.receive().content // Returns GameMessage!

    switch gameMessage {
    case .selectedCharacter(let character):
        print("Character selected: \(character)")
    case .move(let row, let column):
        print("Move: (\(row), \(column))")
    }
}

Supported formats

  • .json — JSON encoding (human-readable, widely compatible)
  • .propertyList — Property list (faster, smaller)

Benefits

  • No JSON boilerplate (~50 lines → ~10 lines)
  • Type-safe (compiler catches message structure changes)
  • Automatic framing (handles message boundaries)

When to use

  • App-to-app communication (you control both ends)
  • Prototyping (fastest time to working code)
  • Type-safe protocols

When NOT to use

  • Interoperating with non-Swift servers
  • Need custom wire format
  • Performance-critical (prefer manual encoding for control)

4.6 NetworkListener (iOS 26+)

Listen for incoming connections with automatic subtask management.

Example: Listening for Connections (WWDC 15:16)

import Network

// Listener with Coder protocol
public func listenForIncomingConnections() async throws {
    try await NetworkListener {
        Coder(GameMessage.self, using: .json) {
            TLS()
        }
    }.run { connection in
        // Each connection gets its own subtask
        for try await (gameMessage, _) in connection.messages {
            switch gameMessage {
            case .selectedCharacter(let character):
                print("Player chose: \(character)")
            case .move(let row, let column):
                print("Player moved: (\(row), \(column))")
            }
        }
    }
}

Key features

  • Automatic subtask per connection (no manual Task management)
  • Structured concurrency (all subtasks cancelled when listener exits)
  • connection.messages async sequence for receiving

Listener configuration

// Specify port
NetworkListener(port: 1029) { TLS() }

// Let system choose port
NetworkListener { TLS() }

// Bonjour advertising
NetworkListener(service: .init(name: "MyApp", type: "_myapp._tcp")) { TLS() }

4.7 NetworkBrowser & Wi-Fi Aware (iOS 26+)

Discover endpoints on local network or nearby devices.

Example: Wi-Fi Aware Discovery (WWDC 17:39)

import Network
import WiFiAware

// Browse for nearby paired Wi-Fi Aware devices
public func findNearbyDevice() async throws {
    let endpoint = try await NetworkBrowser(
        for: .wifiAware(.connecting(to: .allPairedDevices, from: .ticTacToeService))
    ).run { endpoints in
        .finish(endpoints.first!) // Use first discovered device
    }

    // Make connection to the discovered endpoint
    let connection = NetworkConnection(to: endpoint) {
        Coder(GameMessage.self, using: .json) {
            TLS()
        }
    }
}

Wi-Fi Aware features

  • Peer-to-peer without infrastructure (no WiFi router needed)
  • Automatic discovery of paired devices
  • Low latency, high throughput
  • iOS 26+ only

Browse descriptors

// Bonjour
.bonjour(type: "_http._tcp", domain: "local")

// Wi-Fi Aware (all paired devices)
.wifiAware(.connecting(to: .allPairedDevices, from: .myService))

// Wi-Fi Aware (specific device)
.wifiAware(.connecting(to: .pairedDevice(identifier: deviceID), from: .myService))

NWConnection (iOS 12-25) Complete Reference

5.1 Creating Connections

NWConnection uses completion handlers (pre-async/await).

Basic TLS Connection (WWDC 2018 lines 133-166)

import Network

// Create connection
let connection = NWConnection(
    host: NWEndpoint.Host("mail.example.com"),
    port: NWEndpoint.Port(integerLiteral: 993),
    using: .tls // TCP inferred
)

// Handle connection state changes
connection.stateUpdateHandler = { [weak self] state in
    switch state {
    case .ready:
        print("Connection established")
        self?.sendData()

    case .waiting(let error):
        print("Waiting for network: \(error)")
        // Show "Waiting..." UI, don't fail immediately

    case .failed(let error):
        print("Connection failed: \(error)")

    case .cancelled:
        print("Connection cancelled")

    default:
        break
    }
}

// Start connection
connection.start(queue: .main)

Critical Always use [weak self] in stateUpdateHandler to prevent retain cycles.

Custom Parameters

// Create custom parameters
let parameters = NWParameters.tls

// Prohibit expensive networks
parameters.prohibitExpensivePaths = true // Don't use cellular/hotspot

// Prohibit constrained networks
parameters.prohibitConstrainedPaths = true // Respect low data mode

// Require IPv6
parameters.requiredInterfaceType = .wifi
parameters.ipOptions.version = .v6

let connection = NWConnection(host: "example.com", port: 443, using: parameters)

5.2 State Handling

NWConnection state machine (same as NetworkConnection):

setup → preparing → waiting/ready → failed/cancelled

State handling best practices

connection.stateUpdateHandler = { [weak self] state in
    guard let self = self else { return }

    switch state {
    case .preparing:
        // DNS lookup, TCP SYN, TLS handshake in progress
        self.updateUI(.connecting)

    case .waiting(let error):
        // Network unavailable or blocked
        // DON'T fail immediately, framework retries automatically
        print("Waiting: \(error.localizedDescription)")
        self.updateUI(.waiting)

    case .ready:
        // Connection established, can send/receive
        self.updateUI(.connected)
        self.startSending()

    case .failed(let error):
        // Unrecoverable error after all retry attempts
        print("Failed: \(error.localizedDescription)")
        self.updateUI(.failed)

    case .cancelled:
        // connection.cancel() called
        self.updateUI(.disconnected)

    default:
        break
    }
}

5.3 Send/Receive with Callbacks

Send with Pacing (WWDC 2018 lines 320-341)

// Send with contentProcessed callback for pacing
func sendData() {
    let data = Data("Hello, world!".utf8)

    connection.send(content: data, completion: .contentProcessed { [weak self] error in
        if let error = error {
            print("Send error: \(error)")
            return
        }

        // contentProcessed = network stack consumed data
        // NOW send next chunk (pacing)
        self?.sendNextData()
    })
}

contentProcessed callback Invoked when network stack consumes your data (equivalent to when blocking socket call would return). Use this for pacing to avoid buffering excessive data.

Receive with Exact Byte Count

// Receive exactly 10 bytes
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.count) bytes")
        // Process data...

        // Continue receiving
        self?.receiveMore()
    }
}

Receive parameters

  • minimumIncompleteLength: Minimum bytes before callback (1 = return any data)
  • maximumLength: Maximum bytes per callback
  • For "exactly n bytes": Set both to n

5.4 UDP Batching (WWDC 2018 lines 343-347)

Batch sending for 30% CPU reduction.

// UDP connection
let connection = NWConnection(
    host: NWEndpoint.Host("game-server.example.com"),
    port: NWEndpoint.Port(integerLiteral: 9000),
    using: .udp
)

connection.start(queue: .main)

// Batch multiple datagrams
func sendVideoFrames(_ frames: [Data]) {
    connection.batch {
        for frame in frames {
            connection.send(content: frame, completion: .contentProcessed { error in
                if let error = error {
                    print("Send error: \(error)")
                }
            })
        }
    }
    // All sends batched into ~1 syscall
    // Result: 30% lower CPU usage vs individual sends
}

Without batch 100 datagrams = 100 syscalls = high CPU With batch 100 datagrams = ~1 syscall = 30% lower CPU (measured with Instruments)


5.5 NWListener (WWDC 2018 lines 233-293)

Accept incoming connections.

import Network

// Create listener on port 1029
let listener = try NWListener(using: .tcp, on: 1029)

// Advertise Bonjour service
listener.service = NWListener.Service(name: "MyApp", type: "_myapp._tcp")

// Handle service registration
listener.serviceRegistrationUpdateHandler = { update in
    switch update {
    case .add(let endpoint):
        if case .service(let name, let type, let domain, _) = endpoint {
            print("Advertising: \(name).\(type)\(domain)")
        }
    default:
        break
    }
}

// Handle new connections
listener.newConnectionHandler = { [weak self] newConnection in
    print("New connection from: \(newConnection.endpoint)")

    newConnection.stateUpdateHandler = { state in
        if case .ready = state {
            print("Client connected")
            self?.handleClient(newConnection)
        }
    }

    newConnection.start(queue: .main)
}

// Handle listener state
listener.stateUpdateHandler = { state in
    switch state {
    case .ready:
        print("Listener ready on port \(listener.port ?? 0)")
    case .failed(let error):
        print("Listener failed: \(error)")
    default:
        break
    }
}

// Start listening
listener.start(queue: .main)

5.6 NWBrowser (Bonjour Discovery)

Discover services on local network.

import Network

// Browse for Bonjour services
let browser = NWBrowser(
    for: .bonjour(type: "_http._tcp", domain: nil),
    using: .tcp
)

// Handle discovered services
browser.browseResultsChangedHandler = { results, changes in
    for result in results {
        switch result.endpoint {
        case .service(let name, let type, let domain, _):
            print("Found service: \(name).\(type)\(domain)")

            // Connect to this service
            let connection = NWConnection(to: result.endpoint, using: .tcp)
            connection.start(queue: .main)

        default:
            break
        }
    }
}

// Handle browser state
browser.stateUpdateHandler = { state in
    switch state {
    case .ready:
        print("Browser ready")
    case .failed(let error):
        print("Browser failed: \(error)")
    default:
        break
    }
}

// Start browsing
browser.start(queue: .main)

Mobility & Network Transitions

Connection Viability (WWDC 2018 lines 453-463)

Viability = connection can send/receive data (has valid route).

connection.viabilityUpdateHandler = { isViable in
    if isViable {
        print("✅ Connection viable (can send/receive)")
    } else {
        print("⚠️ Connection not viable (no route)")
        // Don't tear down immediately, may recover
        // Show UI: "Connection interrupted"
    }
}

When viability changes

  • Walk into elevator (WiFi signal lost) → not viable
  • Walk out of elevator (WiFi returns) → viable again
  • Switch WiFi → cellular → not viable briefly → viable on cellular

Best practice Don't tear down connection on viability loss. Framework will recover when network returns.

Better Path Available (WWDC 2018 lines 464-477)

Better path = alternative network with better characteristics.

connection.betterPathUpdateHandler = { betterPathAvailable in
    if betterPathAvailable {
        print("📶 Better path available (e.g., WiFi while on cellular)")
        // Consider migrating to new connection
        self.migrateToNewConnection()
    }
}

Scenarios

  • Connected on cellular, walk into building with WiFi → better path available
  • Connected on WiFi, WiFi quality degrades, cellular available → better path available

Migration pattern

func migrateToNewConnection() {
    // Create new connection
    let newConnection = NWConnection(host: host, port: port, using: parameters)

    newConnection.stateUpdateHandler = { [weak self] state in
        if case .ready = state {
            // New connection ready, switch over
            self?.currentConnection?.cancel()
            self?.currentConnection = newConnection
        }
    }

    newConnection.start(queue: .main)

    // Keep old connection until new one ready
}

Multipath TCP (WWDC 2018 lines 480-487)

Automatically migrate between networks without application intervention.

let parameters = NWParameters.tcp
parameters.multipathServiceType = .handover // Seamless network transition

let connection = NWConnection(host: "example.com", port: 443, using: parameters)

Multipath TCP modes

  • .handover — Seamless handoff between networks (WiFi ↔ cellular)
  • .interactive — Use multiple paths simultaneously (lowest latency)
  • .aggregate — Use multiple paths simultaneously (highest throughput)

Benefits

  • Automatic network transition (no viability handlers needed)
  • No connection interruption when switching networks
  • Fallback to single-path if MPTCP unavailable

NWPathMonitor (WWDC 2018 lines 489-496)

Monitor network state changes (replaces SCNetworkReachability).

import Network

let monitor = NWPathMonitor()

monitor.pathUpdateHandler = { path in
    if path.status == .satisfied {
        print("✅ Network available")

        // Check interface types
        if path.usesInterfaceType(.wifi) {
            print("Using WiFi")
        } else if path.usesInterfaceType(.cellular) {
            print("Using cellular")
        }

        // Check if expensive
        if path.isExpensive {
            print("⚠️ Expensive path (cellular/hotspot)")
        }

    } else {
        print("❌ No network")
    }
}

monitor.start(queue: .main)

Use cases

  • Show "No network" UI when path.status == .unsatisfied
  • Disable high-bandwidth features when path.isExpensive
  • Adjust quality based on interface type

When to use

  • Global network state monitoring
  • When "waiting for connectivity" isn't enough
  • Need to know available interfaces before connecting

When NOT to use

  • Checking before connecting (use waiting state instead)
  • Per-connection monitoring (use viability handlers instead)

Security Configuration

TLS Version

// iOS 13+ requires TLS 1.2+ by default
let tlsOptions = NWProtocolTLS.Options()

// Allow TLS 1.2 and 1.3
tlsOptions.minimumTLSProtocolVersion = .TLSv12

// Require TLS 1.3 only
tlsOptions.minimumTLSProtocolVersion = .TLSv13

let parameters = NWParameters(tls: tlsOptions)
let connection = NWConnection(host: "example.com", port: 443, using: parameters)

Certificate Pinning

// Production-grade certificate pinning
let tlsOptions = NWProtocolTLS.Options()

sec_protocol_options_set_verify_block(
    tlsOptions.securityProtocolOptions,
    { (metadata, trust, complete) in
        // Get server certificate
        let serverCert = sec_protocol_metadata_copy_peer_public_key(metadata)

        // Compare with pinned certificate
        let pinnedCertData = Data(/* your pinned cert */)
        let serverCertData = SecCertificateCopyData(serverCert) as Data

        if serverCertData == pinnedCertData {
            complete(true) // Accept
        } else {
            complete(false) // Reject (prevents MITM attacks)
        }
    },
    .main
)

let parameters = NWParameters(tls: tlsOptions)

Cipher Suites

let tlsOptions = NWProtocolTLS.Options()

// Specify allowed cipher suites
tlsOptions.tlsCipherSuites = [
    tls_ciphersuite_t(rawValue: 0x1301), // TLS_AES_128_GCM_SHA256
    tls_ciphersuite_t(rawValue: 0x1302), // TLS_AES_256_GCM_SHA384
]

// iOS defaults to secure modern ciphers, only customize if required

Performance Optimization

User-Space Networking (WWDC 2018 lines 409-441)

Automatic on iOS/tvOS. Network.framework moves TCP/UDP stack into your app process.

Benefits

  • ~30% lower CPU usage (measured with Instruments)
  • No kernel→userspace copy (memory-mapped regions)
  • Reduced context switches

Legacy vs User-Space

Traditional Sockets User-Space Networking
Packet → driver → kernel → copy → userspace Packet → driver → memory-mapped region → userspace (no copy)
100 datagrams = 100 syscalls 100 datagrams = ~1 syscall (with batching)
~30% higher CPU Baseline CPU

WWDC demo Live UDP video streaming showed 30% CPU difference (sockets vs Network.framework).

ECN for UDP (WWDC 2018 lines 365-378)

Explicit Congestion Notification for smooth UDP transmission.

// Create IP metadata with ECN
let ipMetadata = NWProtocolIP.Metadata()
ipMetadata.ecnFlag = .congestionEncountered // Or .ect0, .ect1

// Attach to send context
let context = NWConnection.ContentContext(
    identifier: "video_frame",
    metadata: [ipMetadata]
)

connection.send(content: data, contentContext: context, completion: .contentProcessed { _ in })

ECN flags

  • .ect0 / .ect1 — ECN-capable transport
  • .congestionEncountered — Congestion notification received

Benefits Network can signal congestion without dropping packets.

Service Class (WWDC 2018 lines 379-388)

Mark traffic priority.

// Connection-wide service class
let parameters = NWParameters.tcp
parameters.serviceClass = .background // Low priority

let connection = NWConnection(host: "example.com", port: 443, using: parameters)

// Per-packet service class (UDP)
let ipMetadata = NWProtocolIP.Metadata()
ipMetadata.serviceClass = .realTimeInteractive // High priority (voice)

let context = NWConnection.ContentContext(identifier: "voip", metadata: [ipMetadata])
connection.send(content: audioData, contentContext: context, completion: .contentProcessed { _ in })

Service classes

  • .background — Low priority (large downloads, sync)
  • .default — Normal priority
  • .responsiveData — Interactive data (API calls)
  • .realTimeInteractive — Time-sensitive (voice, gaming)

TCP Fast Open (WWDC 2018 lines 389-406)

Send initial data in TCP SYN packet (saves round trip).

let parameters = NWParameters.tcp
parameters.allowFastOpen = true

let connection = NWConnection(host: "example.com", port: 443, using: parameters)

// Send initial data BEFORE calling start()
let initialData = Data("GET / HTTP/1.1\r\n".utf8)
connection.send(
    content: initialData,
    contentContext: .defaultMessage,
    isComplete: false,
    completion: .idempotent // Data is safe to replay
)

// Now start connection (initial data sent in SYN)
connection.start(queue: .main)

Benefits Reduces connection establishment time by 1 RTT. Requirements Data must be idempotent (safe to replay if SYN retransmitted).


Migration Strategies

From BSD Sockets to NWConnection

BSD Sockets NWConnection Notes
socket() + connect() NWConnection(host:port:using:) + start() Non-blocking by default
send() / sendto() connection.send(content:completion:) Async callback
recv() / recvfrom() connection.receive(min:max:completion:) Async callback
bind() + listen() NWListener(using:on:) Automatic port binding
accept() listener.newConnectionHandler Callback per connection
getaddrinfo() Use NWEndpoint.Host(hostname) DNS automatic
SCNetworkReachability connection.stateUpdateHandler waiting state No race conditions
setsockopt() NWParameters Type-safe options

Migration example

Before (blocking sockets)

int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &addr, addrlen); // BLOCKS
send(sock, data, len, 0);

After (NWConnection)

let connection = NWConnection(host: "example.com", port: 443, using: .tls)
connection.stateUpdateHandler = { state in
    if case .ready = state {
        connection.send(content: data, completion: .contentProcessed { _ in })
    }
}
connection.start(queue: .main)

From URLSession StreamTask to NetworkConnection

When to migrate

  • Need UDP (StreamTask only supports TCP)
  • Need custom protocols
  • Need low-level control

When to STAY with URLSession

  • HTTP/HTTPS (URLSession optimized for this)
  • WebSocket support
  • Built-in caching, cookies

Migration example

Before (URLSession StreamTask)

let task = URLSession.shared.streamTask(withHostName: "example.com", port: 443)
task.resume()
task.write(Data("Hello".utf8), timeout: 10) { _ in }

After (NetworkConnection iOS 26+)

let connection = NetworkConnection(to: .hostPort(host: "example.com", port: 443)) { TLS() }
try await connection.send(Data("Hello".utf8))

From NWConnection to NetworkConnection

Benefits of migration

  • Async/await (no callback nesting)
  • No [weak self] needed
  • TLV framing built-in
  • Coder protocol for Codable types

Migration mapping

NWConnection NetworkConnection
connection.stateUpdateHandler = { } for await state in connection.states { }
connection.send(content:completion:) try await connection.send(content)
connection.receive(min:max:completion:) try await connection.receive(exactly:)
Manual JSON Coder(MyType.self, using: .json)
Custom framer TLV { TLS() }
[weak self] everywhere No [weak self] needed

Migration example

Before (NWConnection)

connection.stateUpdateHandler = { [weak self] state in
    if case .ready = state {
        self?.sendData()
    }
}

func sendData() {
    connection.send(content: data, completion: .contentProcessed { [weak self] error in
        self?.receiveData()
    })
}

After (NetworkConnection)

Task {
    for await state in connection.states {
        if case .ready = state {
            try await connection.send(data)
            let received = try await connection.receive(exactly: 10).content
        }
    }
}

Testing Checklist

Before shipping networking code:

Device Testing

  • Tested on real device (not just simulator)
  • Tested on multiple iOS versions (12, 15, 26)
  • Tested on iPhone and iPad (different network characteristics)

Network Conditions

  • WiFi (home network)
  • Cellular (disable WiFi)
  • Airplane Mode → WiFi (test waiting state)
  • WiFi → cellular transition (walk out of building)
  • Cellular → WiFi transition (walk into building)
  • Weak signal (basement, elevator)
  • Network Link Conditioner (100ms latency, 3% packet loss)

Network Types

  • IPv4-only network
  • IPv6-only network (some cellular carriers)
  • Dual-stack (IPv4 + IPv6)
  • Corporate VPN active
  • Personal hotspot (expensive path)

Performance

  • Connection establishment < 500ms (check logs)
  • Using batch for UDP (verify with Instruments)
  • Using contentProcessed for pacing (check send timing)
  • Profiled with Instruments Network template
  • CPU usage acceptable (< 10% for networking)
  • Memory stable (no leaks, check [weak self])

Error Handling

  • Handling .waiting state (show "Waiting..." UI)
  • Handling .failed state (specific error messages)
  • TLS handshake errors logged
  • Timeout handling (don't wait forever)
  • User-facing errors actionable ("Check network" not "POSIX 61")

iOS 26+ Features (if using NetworkConnection)

  • Using TLV framing if need message boundaries
  • Using Coder protocol if sending Codable types
  • Using NetworkListener instead of NWListener
  • Using NetworkBrowser for Wi-Fi Aware if peer-to-peer

API Quick Reference

NetworkConnection (iOS 26+)

// Create connection
NetworkConnection(to: .hostPort(host: "example.com", port: 443)) { TLS() }

// Send
try await connection.send(data)

// Receive
try await connection.receive(exactly: n).content

// States
for await state in connection.states { }

// TLV framing
NetworkConnection(to: endpoint) { TLV { TLS() } }

// Coder protocol
NetworkConnection(to: endpoint) { Coder(MyType.self, using: .json) { TLS() } }

// Listener
NetworkListener { TLS() }.run { connection in }

// Browser
NetworkBrowser(for: .wifiAware(...)).run { endpoints in }

NWConnection (iOS 12-25)

// Create connection
let connection = NWConnection(host: "example.com", port: 443, using: .tls)

// State handler
connection.stateUpdateHandler = { [weak self] state in }

// Start
connection.start(queue: .main)

// Send
connection.send(content: data, completion: .contentProcessed { [weak self] error in })

// Receive
connection.receive(minimumIncompleteLength: min, maximumLength: max) { [weak self] data, context, isComplete, error in }

// Viability
connection.viabilityUpdateHandler = { isViable in }

// Better path
connection.betterPathUpdateHandler = { betterPathAvailable in }

// Cancel
connection.cancel()

NWListener (iOS 12-25)

let listener = try NWListener(using: .tcp, on: 1029)
listener.newConnectionHandler = { newConnection in }
listener.start(queue: .main)

NWBrowser (iOS 12-25)

let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: .tcp)
browser.browseResultsChangedHandler = { results, changes in }
browser.start(queue: .main)

NWPathMonitor

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in }
monitor.start(queue: .main)

Related Resources

WWDC Sessions

  • WWDC 2018/715 "Introducing Network.framework: A modern alternative to Sockets" (NWConnection, user-space networking, deprecations)
  • WWDC 2025/250 "Use structured concurrency with Network framework" (NetworkConnection, TLV, Coder, Wi-Fi Aware)

Apple Documentation

Related Axiom Skills

  • networking — Discipline-enforcing patterns with anti-patterns, pressure scenarios
  • networking-diag — Systematic troubleshooting for connection failures, timeouts, performance issues

Last Updated 2025-12-02 Status Production-ready reference from WWDC 2018 and WWDC 2025 Coverage NWConnection (iOS 12-25), NetworkConnection (iOS 26+), all 12 WWDC 2025 code examples