axiom-network-framework-ref by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-network-framework-refNetwork.framework 是 Apple 用于替代 Berkeley 套接字的现代网络 API,它提供智能连接建立、用户空间网络、内置 TLS 支持和无缝移动性。于 iOS 12 (2018) 引入 NWConnection,并在 iOS 26 (2025) 演变为支持结构化并发的 NetworkConnection。
axiom-networking 了解反模式、常见模式和压力场景axiom-networking-diag 对连接失败进行系统性故障排除广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在以下情况下使用此技能:
| 年份 | iOS 版本 | 关键特性 |
|---|---|---|
| 2018 | iOS 12 | 引入 NWConnection、NWListener、NWBrowser |
| 2019 | iOS 13 | 用户空间网络(CPU 降低 30%),默认 TLS 1.3 |
| 2021 | iOS 15 | URLSession 支持 WebSocket |
| 2025 | iOS 26 | NetworkConnection (async/await)、TLV 帧格式、Coder 协议、Wi-Fi Aware |
| 特性 | NWConnection (iOS 12-25) | NetworkConnection (iOS 26+) |
|---|---|---|
| 异步模型 | 完成处理程序 | async/await 结构化并发 |
| 状态更新 | stateUpdateHandler 回调 | states AsyncSequence |
| 发送 | send(content:completion:) 回调 | try await send(content) 挂起 |
| 接收 | receive(minimumIncompleteLength:maximumLength:completion:) | try await receive(exactly:) 挂起 |
| 帧格式 | 手动或自定义 NWFramer | 内置 TLV (TLV { TLS() }) |
| Codable | 手动 JSON 编码/解码 | Coder 协议 (Coder(MyType.self, using: .json)) |
| 内存 | 所有闭包中都需要 [weak self] | 无需 [weak self](任务取消自动处理) |
| 错误处理 | 在完成回调中检查错误 | throws 并自然传播 |
| 状态机 | 状态变化时回调 | for await state in connection.states |
| 发现 | NWBrowser(仅 Bonjour) | NetworkBrowser(Bonjour + Wi-Fi Aware) |
NetworkConnection 使用声明式协议栈组合。
import Network
// 带 TLS 的基本连接(TCP 和 IP 自动推断)
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
TLS()
}
// 使用 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)")
}
TLS() 自动推断 TCP() 和 IP()// 自定义 IP 分片
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
TLS {
TCP {
IP()
.fragmentationEnabled(false) // 禁用 IP 分片
}
}
}
.fragmentationEnabled(false) — 适用于自行处理分片的协议(如 QUIC).ipVersion(.v6) — 强制仅使用 IPv6(用于测试)// 受限路径(低数据模式)+ 自定义 IP
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029),
using: .parameters {
TLS {
TCP {
IP()
.fragmentationEnabled(false)
}
}
}
.constrainedPathsProhibited(true) // 在低数据模式下不使用蜂窝网络
)
.constrainedPathsProhibited(true) — 遵守低数据模式.expensivePathsProhibited(true) — 不使用蜂窝网络/热点.multipathServiceType(.handover) — 启用多路径 TCP// 主机 + 端口
.hostPort(host: "example.com", port: 443)
// 服务 (Bonjour)
.service(name: "MyPrinter", type: "_ipp._tcp", domain: "local.", interface: nil)
// Unix 域套接字
.unix(path: "/tmp/my.sock")
// TLS over TCP(最常见)
TLS()
// QUIC (TLS + UDP,多路复用流)
QUIC()
// UDP(数据报)
UDP()
// TCP(流,无加密)
TCP()
// WebSocket over TLS
WebSocket {
TLS()
}
// 自定义帧格式
TLV {
TLS()
}
NetworkConnection 经历以下状态转换:
setup
↓
preparing (DNS, TCP 握手, TLS 握手)
↓
┌─ waiting (无网络,重试中)
│ ↓
└→ ready (可发送/接收)
↓
failed (错误) 或 cancelled
// 选项 1:异步序列(在后台监控)
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
}
}
}
let data = Data("Hello".utf8)
try await connection.send(data)
// 接收恰好 98 字节
let incomingData = try await connection.receive(exactly: 98).content
print("Received \(incomingData.count) bytes")
// 读取 UInt32 长度前缀,然后读取相应字节数
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
// 处理块...
}
receive(exactly: n) — 等待恰好 n 字节receive(atLeast: min, atMost: max) — 获取介于 min 和 max 之间的字节receive(as: UInt32.self) — 读取固定大小的类型(网络字节序)TLV(类型-长度-值) 解决了流协议(TCP/TLS)上的消息边界问题。
import Network
// 定义消息类型
enum GameMessage: Int {
case selectedCharacter = 0
case move = 1
}
struct GameCharacter: Codable {
let character: String
}
struct GameMove: Codable {
let row: Int
let column: Int
}
// 带 TLV 帧格式的连接
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
TLV {
TLS()
}
}
// 发送类型化消息
public func sendWithTLV() async throws {
let characterData = try JSONEncoder().encode(GameCharacter(character: "🐨"))
try await connection.send(characterData, type: GameMessage.selectedCharacter.rawValue)
}
// 接收类型化消息
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)")
}
}
Coder 消除了手动 JSON 编码/解码的样板代码。
import Network
// 将消息类型定义为 Codable 枚举
enum GameMessage: Codable {
case selectedCharacter(String)
case move(row: Int, column: Int)
}
// 带 Coder 的连接
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
Coder(GameMessage.self, using: .json) {
TLS()
}
}
// 直接发送 Codable(无需编码!)
public func sendWithCoder() async throws {
let selectedCharacter: GameMessage = .selectedCharacter("🐨")
try await connection.send(selectedCharacter)
}
// 直接接收 Codable(无需解码!)
public func receiveWithCoder() async throws {
let gameMessage = try await connection.receive().content // 返回 GameMessage!
switch gameMessage {
case .selectedCharacter(let character):
print("Character selected: \(character)")
case .move(let row, let column):
print("Move: (\(row), \(column))")
}
}
.json — JSON 编码(人类可读,广泛兼容).propertyList — 属性列表(更快,更小)监听传入连接,并自动管理子任务。
import Network
// 带 Coder 协议的监听器
public func listenForIncomingConnections() async throws {
try await NetworkListener {
Coder(GameMessage.self, using: .json) {
TLS()
}
}.run { connection in
// 每个连接都有自己的子任务
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))")
}
}
}
}
connection.messages 异步序列用于接收// 指定端口
NetworkListener(port: 1029) { TLS() }
// 让系统选择端口
NetworkListener { TLS() }
// Bonjour 广告
NetworkListener(service: .init(name: "MyApp", type: "_myapp._tcp")) { TLS() }
在本地网络或附近设备上发现端点。
import Network
import WiFiAware
// 浏览附近已配对的 Wi-Fi Aware 设备
public func findNearbyDevice() async throws {
let endpoint = try await NetworkBrowser(
for: .wifiAware(.connecting(to: .allPairedDevices, from: .ticTacToeService))
).run { endpoints in
.finish(endpoints.first!) // 使用第一个发现的设备
}
// 连接到发现的端点
let connection = NetworkConnection(to: endpoint) {
Coder(GameMessage.self, using: .json) {
TLS()
}
}
}
// Bonjour
.bonjour(type: "_http._tcp", domain: "local")
// Wi-Fi Aware(所有已配对设备)
.wifiAware(.connecting(to: .allPairedDevices, from: .myService))
// Wi-Fi Aware(特定设备)
.wifiAware(.connecting(to: .pairedDevice(identifier: deviceID), from: .myService))
NWConnection 使用完成处理程序(async/await 之前)。
import Network
// 创建连接
let connection = NWConnection(
host: NWEndpoint.Host("mail.example.com"),
port: NWEndpoint.Port(integerLiteral: 993),
using: .tls // TCP 自动推断
)
// 处理连接状态变化
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
print("Connection established")
self?.sendData()
case .waiting(let error):
print("Waiting for network: \(error)")
// 显示“等待中...”UI,不要立即失败
case .failed(let error):
print("Connection failed: \(error)")
case .cancelled:
print("Connection cancelled")
default:
break
}
}
// 启动连接
connection.start(queue: .main)
关键 始终在 stateUpdateHandler 中使用 [weak self] 以防止循环引用。
// 创建自定义参数
let parameters = NWParameters.tls
// 禁止昂贵网络
parameters.prohibitExpensivePaths = true // 不使用蜂窝网络/热点
// 禁止受限网络
parameters.prohibitConstrainedPaths = true // 遵守低数据模式
// 要求 IPv6
parameters.requiredInterfaceType = .wifi
parameters.ipOptions.version = .v6
let connection = NWConnection(host: "example.com", port: 443, using: parameters)
NWConnection 状态机(与 NetworkConnection 相同):
setup → preparing → waiting/ready → failed/cancelled
connection.stateUpdateHandler = { [weak self] state in
guard let self = self else { return }
switch state {
case .preparing:
// DNS 查找、TCP SYN、TLS 握手进行中
self.updateUI(.connecting)
case .waiting(let error):
// 网络不可用或被阻止
// 不要立即失败,框架会自动重试
print("Waiting: \(error.localizedDescription)")
self.updateUI(.waiting)
case .ready:
// 连接已建立,可发送/接收
self.updateUI(.connected)
self.startSending()
case .failed(let error):
// 所有重试尝试后的不可恢复错误
print("Failed: \(error.localizedDescription)")
self.updateUI(.failed)
case .cancelled:
// 调用了 connection.cancel()
self.updateUI(.disconnected)
default:
break
}
}
// 使用 contentProcessed 回调进行节奏控制
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 = 网络栈已消费数据
// 现在发送下一个数据块(节奏控制)
self?.sendNextData()
})
}
contentProcessed 回调 在网络栈消费您的数据时调用(相当于阻塞套接字调用返回时)。使用此回调进行节奏控制,以避免缓冲过多数据。
// 接收恰好 10 字节
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")
// 处理数据...
// 继续接收
self?.receiveMore()
}
}
minimumIncompleteLength: 回调前的最小字节数(1 = 返回任何数据)maximumLength: 每次回调的最大字节数// UDP 连接
let connection = NWConnection(
host: NWEndpoint.Host("game-server.example.com"),
port: NWEndpoint.Port(integerLiteral: 9000),
using: .udp
)
connection.start(queue: .main)
// 批处理多个数据报
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)")
}
})
}
}
// 所有发送批处理到约 1 个系统调用
// 结果:相比单独发送降低 30% CPU 使用率
}
无批处理 100 个数据报 = 100 个系统调用 = 高 CPU 有批处理 100 个数据报 = 约 1 个系统调用 = 降低 30% CPU(使用 Instruments 测量)
接受传入连接。
import Network
// 在端口 1029 上创建监听器
let listener = try NWListener(using: .tcp, on: 1029)
// 广告 Bonjour 服务
listener.service = NWListener.Service(name: "MyApp", type: "_myapp._tcp")
// 处理服务注册
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
}
}
// 处理新连接
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)
}
// 处理监听器状态
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
}
}
// 开始监听
listener.start(queue: .main)
发现本地网络上的服务。
import Network
// 浏览 Bonjour 服务
let browser = NWBrowser(
for: .bonjour(type: "_http._tcp", domain: nil),
using: .tcp
)
// 处理发现的服务
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)")
// 连接到此服务
let connection = NWConnection(to: result.endpoint, using: .tcp)
connection.start(queue: .main)
default:
break
}
}
}
// 处理浏览器状态
browser.stateUpdateHandler = { state in
switch state {
case .ready:
print("Browser ready")
case .failed(let error):
print("Browser failed: \(error)")
default:
break
}
}
// 开始浏览
browser.start(queue: .main)
可用性 = 连接可以发送/接收数据(有有效路由)。
connection.viabilityUpdateHandler = { isViable in
if isViable {
print("✅ Connection viable (can send/receive)")
} else {
print("⚠️ Connection not viable (no route)")
// 不要立即拆除,可能恢复
// 显示 UI:“连接中断”
}
}
最佳实践 不要在可用性丢失时拆除连接。当网络恢复时,框架会恢复连接。
更好的路径 = 具有更好特性的替代网络。
connection.betterPathUpdateHandler = { betterPathAvailable in
if betterPathAvailable {
print("📶 Better path available (e.g., WiFi while on cellular)")
// 考虑迁移到新连接
self.migrateToNewConnection()
}
}
func migrateToNewConnection() {
// 创建新连接
let newConnection = NWConnection(host: host, port: port, using: parameters)
newConnection.stateUpdateHandler = { [weak self] state in
if case .ready = state {
// 新连接就绪,切换过去
self?.currentConnection?.cancel()
self?.currentConnection = newConnection
}
}
newConnection.start(queue: .main)
// 保持旧连接直到新连接就绪
}
无需应用干预即可在网络间自动迁移。
let parameters = NWParameters.tcp
parameters.multipathServiceType = .handover // 无缝网络切换
let connection = NWConnection(host: "example.com", port: 443, using: parameters)
.handover — 网络间无缝切换(WiFi ↔ 蜂窝网络).interactive — 同时使用多个路径(最低延迟).aggregate — 同时使用多个路径(最高吞吐量)监控网络状态变化(替代 SCNetworkReachability)。
import Network
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("✅ Network available")
// 检查接口类型
if path.usesInterfaceType(.wifi) {
print("Using WiFi")
} else if path.usesInterfaceType(.cellular) {
print("Using cellular")
}
// 检查是否昂贵
if path.isExpensive {
print("⚠️ Expensive path (cellular/hotspot)")
}
} else {
print("❌ No network")
}
}
monitor.start(queue: .main)
// iOS 13+ 默认要求 TLS 1.2+
let tlsOptions = NWProtocolTLS.Options()
// 允许 TLS 1.2 和 1.3
tlsOptions.minimumTLSProtocolVersion = .TLSv12
// 仅要求 TLS 1.3
tlsOptions.minimumTLSProtocolVersion = .TLSv13
let parameters = NWParameters(tls: tlsOptions)
let connection = NWConnection(host: "example.com", port: 443, using: parameters)
// 生产级证书锁定
let tlsOptions = NWProtocolTLS.Options()
sec_protocol_options_set_verify_block(
tlsOptions.securityProtocolOptions,
{ (metadata, trust, complete) in
// 获取服务器证书
let serverCert = sec_protocol_metadata_copy_peer_public_key(metadata)
// 与锁定的证书比较
let pinnedCertData = Data(/* your pinned cert */)
let serverCertData = SecCertificateCopyData(serverCert) as Data
if serverCertData == pinnedCertData {
complete(true) // 接受
} else {
complete(false) // 拒绝(防止 MITM 攻击)
}
},
.main
)
let parameters = NWParameters(tls: tlsOptions)
企业网络通常使用 TLS 检查代理,这些代理会提供自己的证书。严格的锁定会破坏这些环境。
策略:针对公钥(SPKI)而非完整证书进行锁定,并提供配置逃生舱口:
sec_protocol_options_set_verify_block(
tlsOptions.securityProtocolOptions,
{ (metadata, trust, complete) in
// 1. 检查系统是否信任证书链(处理企业 CA)
let secTrust = sec_trust_copy_ref(trust).takeRetainedValue()
SecTrustEvaluateAsyncWithError(secTrust, .main) { _, result, _ in
guard result else { complete(false); return }
// 2. 如果启用锁定,则同时验证公钥
if PinningConfig.isEnabled {
let serverKey = SecTrustCopyKey(secTrust)
let matches = pinnedKeys.contains { $0 == serverKey }
complete(matches)
} else {
complete(true) // 仅系统信任(企业模式)
}
}
},
.main
)
规则:
SecTrustEvaluateAsyncWithError)——这尊重企业安装的根 CAlet tlsOptions = NWProtocolTLS.Options()
// 指定允许的密码套件
tlsOptions.tlsCipherSuites = [
tls_ciphersuite_t(rawValue: 0x1301), // TLS_AES_128_GCM_SHA256
tls_ciphersuite_t(rawValue: 0x1302), // TLS_AES_256_GCM_SHA384
]
// iOS 默认使用安全的现代密码套件,仅在需要时自定义
在 iOS/tvOS 上自动启用。 Network.framework 将 TCP/UDP 栈移入您的应用进程。
| 传统套接字 | 用户空间网络 |
|---|---|
| 数据包 → 驱动 → 内核 → 复制 → 用户空间 | 数据包 → 驱动 → 内存映射区域 → 用户空间(无复制) |
| 100 个数据报 = 100 个系统调用 | 100 个数据报 = 约 1 个系统调用(带批处理) |
| 约高 30% CPU | 基准 CPU |
WWDC 演示 实时 UDP 视频流显示 30% CPU 差异(套接字 vs Network.framework)。
显式拥塞通知,用于平滑 UDP 传输。
// 创建带 ECN 的 IP 元数据
let ipMetadata = NWProtocolIP.Metadata()
ipMetadata.ecnFlag = .congestionEncountered // 或 .ect0, .ect1
// 附加到发送上下文
let context = NWConnection.ContentContext(
identifier: "video_frame",
metadata: [ipMetadata]
)
connection.send(content: data, contentContext: context, completion: .contentProcessed { _ in })
.ect0 / .ect1 — 支持 ECN 的传输.congestionEncountered — 收到拥塞通知优势 网络可以在不丢弃数据包的情况下发出拥塞信号。
标记流量优先级。
// 连接范围的服务类别
let parameters = NWParameters.tcp
parameters.serviceClass = .background // 低优先级
let connection = NWConnection(host: "example.com",
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.
axiom-networking for anti-patterns, common patterns, pressure scenariosaxiom-networking-diag for systematic troubleshooting of connection failuresUse this skill when:
| 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 |
| 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:) |
NetworkConnection uses declarative protocol stack composition.
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)")
}
TLS() infers TCP() and IP() automatically// Customize IP fragmentation
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
TLS {
TCP {
IP()
.fragmentationEnabled(false) // Disable IP fragmentation
}
}
}
.fragmentationEnabled(false) — For protocols that handle fragmentation themselves (QUIC).ipVersion(.v6) — Force IPv6 only (testing)// 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
)
.constrainedPathsProhibited(true) — Respect low data mode.expensivePathsProhibited(true) — Don't use cellular/hotspot.multipathServiceType(.handover) — Enable Multipath TCP// 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")
// 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()
}
NetworkConnection transitions through these states:
setup
↓
preparing (DNS, TCP handshake, TLS handshake)
↓
┌─ waiting (no network, retrying)
│ ↓
└→ ready (can send/receive)
↓
failed (error) or cancelled
// 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
}
}
}
let data = Data("Hello".utf8)
try await connection.send(data)
// Receive exactly 98 bytes
let incomingData = try await connection.receive(exactly: 98).content
print("Received \(incomingData.count) bytes")
// 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(exactly: n) — Wait for exactly n bytesreceive(atLeast: min, atMost: max) — Get between min and max bytesreceive(as: UInt32.self) — Read fixed-size type (network byte order)TLV (Type-Length-Value) solves message boundary problem on stream protocols (TCP/TLS).
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)")
}
}
Coder eliminates manual JSON encoding/decoding boilerplate.
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))")
}
}
.json — JSON encoding (human-readable, widely compatible).propertyList — Property list (faster, smaller)Listen for incoming connections with automatic subtask management.
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))")
}
}
}
}
connection.messages async sequence for receiving// Specify port
NetworkListener(port: 1029) { TLS() }
// Let system choose port
NetworkListener { TLS() }
// Bonjour advertising
NetworkListener(service: .init(name: "MyApp", type: "_myapp._tcp")) { TLS() }
Discover endpoints on local network or nearby devices.
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()
}
}
}
// 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 uses completion handlers (pre-async/await).
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.
// 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)
NWConnection state machine (same as NetworkConnection):
setup → preparing → waiting/ready → failed/cancelled
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
}
}
// 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 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()
}
}
minimumIncompleteLength: Minimum bytes before callback (1 = return any data)maximumLength: Maximum bytes per callback// 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)
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)
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)
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"
}
}
Best practice Don't tear down connection on viability loss. Framework will recover when network returns.
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()
}
}
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
}
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)
.handover — Seamless handoff between networks (WiFi ↔ cellular).interactive — Use multiple paths simultaneously (lowest latency).aggregate — Use multiple paths simultaneously (highest throughput)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)
// 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)
// 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)
Corporate networks often use TLS inspection proxies that present their own certificates. Strict pinning breaks these environments.
Strategy : Pin against the public key (SPKI) rather than the full certificate, and provide a configuration escape hatch:
sec_protocol_options_set_verify_block(
tlsOptions.securityProtocolOptions,
{ (metadata, trust, complete) in
// 1. Check if system trusts the certificate chain (handles corporate CAs)
let secTrust = sec_trust_copy_ref(trust).takeRetainedValue()
SecTrustEvaluateAsyncWithError(secTrust, .main) { _, result, _ in
guard result else { complete(false); return }
// 2. If pinning enabled, also verify public key
if PinningConfig.isEnabled {
let serverKey = SecTrustCopyKey(secTrust)
let matches = pinnedKeys.contains { $0 == serverKey }
complete(matches)
} else {
complete(true) // System trust only (enterprise mode)
}
}
},
.main
)
Rules :
SecTrustEvaluateAsyncWithError) — this respects enterprise-installed root CAslet 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
Automatic on iOS/tvOS. Network.framework moves TCP/UDP stack into your app process.
| 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).
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 })
.ect0 / .ect1 — ECN-capable transport.congestionEncountered — Congestion notification receivedBenefits Network can signal congestion without dropping packets.
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 })
.background — Low priority (large downloads, sync).default — Normal priority.responsiveData — Interactive data (API calls).realTimeInteractive — Time-sensitive (voice, gaming)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).
| 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 |
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &addr, addrlen); // BLOCKS
send(sock, data, len, 0);
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)
let task = URLSession.shared.streamTask(withHostName: "example.com", port: 443)
task.resume()
task.write(Data("Hello".utf8), timeout: 10) { _ in }
let connection = NetworkConnection(to: .hostPort(host: "example.com", port: 443)) { TLS() }
try await connection.send(Data("Hello".utf8))
[weak self] needed| 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) |
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()
})
}
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
}
}
}
Before shipping networking code:
// 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 }
// 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()
let listener = try NWListener(using: .tcp, on: 1029)
listener.newConnectionHandler = { newConnection in }
listener.start(queue: .main)
let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: .tcp)
browser.browseResultsChangedHandler = { results, changes in }
browser.start(queue: .main)
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in }
monitor.start(queue: .main)
WWDC : 2018-715, 2025-250
Docs : /network, /network/nwconnection, /network/networkconnection
Skills : axiom-networking, axiom-networking-diag
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
Weekly Installs
91
Repository
GitHub Stars
601
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode75
codex70
claude-code70
gemini-cli69
cursor68
github-copilot65
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) |
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 |
| Custom framer | TLV { TLS() } |
[weak self] everywhere | No [weak self] needed |