mcpField
A
axiom-networking-migration

axiom-networking-migration

github.com/charleswiltgenDevelopment1.2k163 installsUpdated today
Repository

About axiom-networking-migration

A migration guide for Swift developers choosing and moving between networking APIs. It covers BSD Sockets, NWConnection, NetworkConnection, and URLSession StreamTask, including when to keep URLSession for HTTP.

Key features

  1. 01BSD Sockets to NWConnection migration
  2. 02NWConnection to NetworkConnection migration
  3. 03URLSession StreamTask migration guidance
  4. 04API mappings and code examples
  5. 05Networking API selection guidance

Use cases

  1. 01Replacing socket code with NWConnection
  2. 02Adopting async/await for connections
  3. 03Migrating for UDP or custom protocols
  4. 04Deciding whether to keep URLSession for HTTP

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-networking-migration

After installing, type/axiom-networking-migrationin Claude Code to confirm it loaded.

Details

From the project README

Network Framework Migration Guides

Do I Need to Migrate?

What networking API are you using?

├─ URLSession for HTTP/HTTPS REST APIs?
│   └─ Stay with URLSession — it's the RIGHT tool for HTTP
│      URLSession handles caching, cookies, auth challenges,
│      HTTP/2/3, and is heavily optimized for web APIs.
│      Network.framework is for custom protocols, NOT HTTP.
│
├─ BSD Sockets (socket, connect, send, recv)?
│   └─ Migrate to NWConnection (iOS 12+)
│      → See Migration 1 below
│
├─ NWConnection / NWListener?
│   ├─ Need async/await? → Migrate to NetworkConnection (iOS 26+)
│   │   → See Migration 2 below
│   └─ Callback-based code working fine? → Stay (not deprecated)
│
├─ URLSession StreamTask for TCP/TLS?
│   └─ Need UDP or custom protocols? → NetworkConnection
│      Need just TCP/TLS for HTTP? → Stay with URLSession
│      → See Migration 3 below
│
├─ SCNetworkReachability?
│   └─ DEPRECATED — Replace with NWPathMonitor (iOS 12+)
│      let monitor = NWPathMonitor()
│      monitor.pathUpdateHandler = { path in
│          print(path.status == .satisfied ? "Online" : "Offline")
│      }
│
└─ CFSocket / NSStream?
    └─ DEPRECATED — Replace with NWConnection (iOS 12+)
       → See Migration 1 below

Migration 1: From BSD Sockets to NWConnection

Migration mapping
BSD SocketsNWConnectionNotes
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.newConnectionHandlerCallback for each connection
getaddrinfo()Let NWConnection handle DNSSmart resolution with racing
SCNetworkReachabilityconnection.stateUpdateHandler waiting stateNo race conditions
setsockopt()NWParameters configurationType-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<addrinfo>?
getaddrinfo("example.com", "443", &hints, &results)

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/decodeCoder(MyType.self, using: .json)Built-in Codable support
Custom framerTLV { TLS() }Built-in Type-Length-Value
[weak self] everywhereNo [weak self] neededTask 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

Comments

Markdown supported