axiom-networking-migration
SKILL.md
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 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<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/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
Weekly Installs
52
Repository
charleswiltgen/axiomFirst Seen
Jan 21, 2026
Security Audits
Installed on
claude-code42
opencode39
codex33
gemini-cli32
cursor32
antigravity30