关于 axiom-network-framework-ref
面向 Apple 平台开发者的 Network.framework API 参考技能,涵盖从 NWConnection 到 iOS 26 NetworkConnection 的用法与迁移,并提供连接、消息分帧和设备发现示例。
主要功能
- 01对比 NWConnection 与 NetworkConnection
- 02配置 TCP、UDP、TLS 和 QUIC 连接
- 03使用 async/await 发送和接收数据
- 04通过 TLV 与 Coder 处理消息
- 05设置监听器及 Bonjour、Wi-Fi Aware 发现
适用场景
- 01将 BSD 套接字或旧 API 迁移至 Network.framework
- 02为自定义协议选择传输方式
- 03实现点对点通信或设备发现
- 04从回调式连接代码迁移到 async/await
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-network-framework-ref安装后在 Claude Code 中输入/axiom-network-framework-ref确认已加载。
详细介绍
Network.framework API 参考
概述
Network.framework 是 Apple 的现代网络 API,可取代 Berkeley 套接字,提供智能连接建立、用户空间网络、内置 TLS 支持和无缝移动性。它于 iOS 12(2018 年)随 NWConnection 引入,并在 iOS 26(2025 年)通过支持结构化并发的 NetworkConnection 得到演进。
演进时间线
- 2018(iOS 12) NWConnection 支持完成处理程序,弃用 CFSocket/NSStream/SCNetworkReachability
- 2019(iOS 13) 用户空间网络(CPU 占用降低 30%),默认使用 TLS 1.3
- 2025(iOS 26) NetworkConnection 支持 async/await,内置 TLV 分帧、Coder 协议、Wi-Fi Aware 发现
核心功能
- 智能连接建立 Happy Eyeballs(IPv4/IPv6 竞速)、代理评估(PAC)、VPN 检测、WiFi Assist 回退
- 用户空间网络 与套接字相比,CPU 占用降低约 30%,支持内存映射区域,减少上下文切换
- 内置安全性 默认使用 TLS 1.3,支持 UDP 的 DTLS、证书固定
- 移动性 自动处理网络切换(WiFi ↔ 蜂窝网络)、可用性通知、Multipath TCP
- 性能 ECN(显式拥塞通知)、服务类别标记、TCP Fast Open、UDP 批处理
何时使用 Network.framework 而非 URLSession
- URLSession HTTP、HTTPS、WebSocket、简单的 TCP/TLS 流 → 使用 URLSession(针对这些场景进行了优化)
- Network.framework UDP、自定义协议、底层控制、点对点、游戏、流媒体 → 使用 Network.framework
相关技能
- 使用
axiom-networking了解反模式、常见模式和压力场景 - 使用
axiom-networking-diag系统地排查连接故障
何时使用此技能
在以下情况下使用此技能:
- 规划迁移,从 BSD 套接字、CFSocket、NSStream 或 SCNetworkReachability 迁移
- 了解 API 差异,比较 NWConnection(iOS 12-25)与 NetworkConnection(iOS 26+)
- 实现 WWDC 2025 的全部 12 个示例(TLS 连接、TLV 分帧、Coder 协议、NetworkListener、Wi-Fi Aware)
- 选择协议(TCP、UDP、TLS、QUIC)以满足使用场景需求
- 设置点对点发现,使用 NetworkBrowser 和 Wi-Fi Aware
- 优化性能,使用用户空间网络、批处理和 pacing
- 迁移,从完成处理程序迁移到 async/await(NWConnection → NetworkConnection)
API 演进
时间线
| 年份 | 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+)对比
| 功能 | 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](自动取消 Task) |
| 错误处理 | 在完成处理程序中检查错误 | 使用 throws 自然传播错误 |
| 状态机 | 状态变化时触发回调 | for await state in connection.states |
| 发现 | NWBrowser(仅 Bonjour) | NetworkBrowser(Bonjour + Wi-Fi Aware) |
建议
- 面向 iOS 26 及更高版本的新应用:使用 NetworkConnection(更简洁、更安全)
- 支持 iOS 12–25 的应用:使用 NWConnection(向后兼容)
- 迁移:两个 API 可以共存,可逐步迁移
NetworkConnection(iOS 26+)完整参考
4.1 创建连接
NetworkConnection 使用声明式协议栈组合。
示例 1:基本 TLS 连接(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)")
}
要点
TLS()会自动推断TCP()和IP()- 无需显式调用 connection.start()(首次发送或接收时会自动启动)
- async/await 消除了回调嵌套
示例 2:自定义 IP 选项(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
}
}
}
何时自定义 IP
.fragmentationEnabled(false)— 用于自行处理分片的协议(QUIC).ipVersion(.v6)— 仅强制使用 IPv6(测试)
示例 3:自定义参数(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
)
常用参数
.constrainedPathsProhibited(true)— 遵循低数据模式设置.expensivePathsProhibited(true)— 不使用蜂窝网络或热点.multipathServiceType(.handover)— 启用 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()
}
4.2 状态机
NetworkConnection 会经历以下状态:
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
}
}
}
关键状态
- .preparing 正在进行 DNS 查询、TCP SYN 和 TLS 握手
- .waiting 当前没有可用网络,框架会自动重试
- .ready 连接已建立,可以发送和接收
- .failed 发生不可恢复的错误(服务器拒绝连接、TLS 失败、超时)
- .cancelled 任务已取消或调用了 connection.cancel()
4.3 发送/接收模式
发送:基本用法
let data = Data("Hello".utf8)
try await connection.send(data)
接收:指定字节数(WWDC 7:30)
// Receive exactly 98 bytes
let incomingData = try await connection.receive(exactly: 98).content
print("Received \(incomingData.count) bytes")
接收:可变长度(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() 变体
receive(exactly: n)— 等待接收恰好 n 个字节receive(atLeast: min, atMost: max)— 接收 min 到 max 个字节receive(as: UInt32.self)— 读取固定大小的类型(网络字节序)
4.4 TLV 帧格式(iOS 26+)
TLV(类型-长度-值) 用于解决流协议(TCP/TLS)中的消息边界问题。
格式
- 类型:UInt32(消息标识符)
- 长度:UInt32(消息大小,自动处理)
- 值:消息字节
示例:使用 TLV 的 GameMessage(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)")
}
}
优点
- 保留消息边界(发送 3 条消息 → 恰好接收 3 条)
- 类型安全的消息处理(基于枚举的路由)
- 开销极小(每条消息 8 字节:类型 + 长度)
适用场景
- 混合消息类型(聊天 + 在线状态 + 正在输入)
- 使用 TLV 的现有协议
- 需要消息边界但不需要复杂帧格式
4.5 Coder 协议(iOS 26+)
Coder 无需手动编写 JSON 编码/解码样板代码。
示例:使用 Coder 的 GameMessage(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))")
}
}
支持的格式
.json— JSON 编码(易于阅读,兼容性广).propertyList— 属性列表(速度更快,占用更少)
优点
- 无需编写 JSON 样板代码(约 50 行 → 约 10 行)
- 类型安全(编译器会捕获消息结构的变更)
- 自动帧处理(处理消息边界)
适用场景
- 应用间通信(通信双方均由你控制)
- 原型开发(最快编写出可运行代码)
- 类型安全的协议
不适用场景
- 需要与非 Swift 服务器互通
- 需要自定义线格式
- 对性能要求极高(为获得控制权,优先采用手动编码)
4.6 NetworkListener(iOS 26+)
监听传入连接,并自动管理子任务。
示例:监听连接(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))")
}
}
}
}
主要功能
- 为每个连接自动创建子任务(无需手动管理 Task)
- 结构化并发(监听器退出时会取消所有子任务)
- 使用
connection.messages异步序列接收数据
监听器配置
// 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+)
发现本地网络或附近设备上的端点。
示例:Wi-Fi Aware 发现(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 功能
- 无需基础设施的点对点通信(无需 WiFi 路由器)
- 自动发现配对设备
- 低延迟,极高吞吐量
- 仅适用于 iOS 26+
浏览描述符
// 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)完整参考
5.1 创建连接
NWConnection 使用完成处理程序(尚未支持 async/await)。
基本 TLS 连接(WWDC 2018 第 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)
重要提示: 始终在 stateUpdateHandler 中使用 [weak self],以防止循环引用。
自定义参数
// 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 状态处理
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 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 使用回调发送/接收
发送时进行速率控制(WWDC 2018 第 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 回调:网络栈处理完数据后调用(相当于阻塞式套接字调用返回时)。利用它进行速率控制,避免缓冲过多数据。
接收指定字节数
// 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:触发回调前所需的最少字节数(1 = 返回任意数据)maximumLength:每次回调的最大字节数- 要“精确接收 n 个字节”:将两者都设为 n
5.4 UDP 批处理(WWDC 2018 第 343-347 行)
批量发送可减少 30% 的 CPU 使用率。
// 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
}
不使用批处理:100 个数据报 = 100 次系统调用 = 高 CPU 使用率 使用批处理:100 个数据报 ≈ 1 次系统调用 = CPU 使用率降低 30%(使用 Instruments 测量)
5.5 NWListener(WWDC 2018 第 233-293 行)
接受传入连接。
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 发现)
发现本地网络上的服务。
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)
移动性与网络切换
连接可用性(WWDC 2018 第 453-463 行)
可用性 = 连接可以发送/接收数据(存在有效路由)。
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"
}
}
可用性变化时
- 走进电梯(WiFi 信号丢失)→ 不可用
- 走出电梯(WiFi 信号恢复)→ 再次可用
- 从 WiFi 切换到蜂窝网络 → 短暂不可用 → 蜂窝网络上恢复可用
最佳实践: 可用性丢失时不要拆除连接。网络恢复后,框架会自动恢复连接。
有更优路径可用(WWDC 2018 第 464-477 行)
更优路径 = 特性更好的替代网络。
connection.betterPathUpdateHandler = { betterPathAvailable in
if betterPathAvailable {
print("📶 Better path available (e.g., WiFi while on cellular)")
// Consider migrating to new connection
self.migrateToNewConnection()
}
}
场景
- 通过蜂窝网络连接,走进有 WiFi 的建筑 → 有更优路径可用
- 通过 WiFi 连接,WiFi 质量下降且有蜂窝网络可用 → 有更优路径可用
迁移模式
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 第 480-487 行)
无需应用程序干预,即可在网络之间自动迁移。
let parameters = NWParameters.tcp
parameters.multipathServiceType = .handover // Seamless network transition
let connection = NWConnection(host: "example.com", port: 443, using: parameters)
Multipath TCP 模式
.handover— 在网络之间无缝切换(WiFi ↔ 蜂窝网络).interactive— 同时使用多条路径(最低延迟).aggregate— 同时使用多条路径(最高吞吐量)
优点
- 自动切换网络(无需可用性处理程序)
- 切换网络时不会中断连接
- MPTCP 不可用时回退到单路径模式
NWPathMonitor(WWDC 2018 第 489-496 行)
监控网络状态变化(取代 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)
使用场景
- 当 path.status == .unsatisfied 时显示“无网络”界面
- 当 path.isExpensive 时禁用高带宽功能
- 根据接口类型调整质量
适用情况
- 监控全局网络状态
- “等待连接”不足以满足需求时
- 连接前需要了解可用接口时
不适用情况
- 连接前进行检查(改用等待状态)
- 监控单个连接(改用可用性处理程序)
安全配置
TLS 版本
// 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)
证书固定与企业代理
企业网络通常会使用 TLS 检查代理,并提供自己的证书。严格的固定机制会导致这些环境无法正常工作。
策略:固定公钥(SPKI),而不是完整证书,并提供可调整配置:
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
)
规则:
- 始终先验证系统信任(
SecTrustEvaluateAsyncWithError)——这样会信任企业安装的根 CA - 使用公钥固定,而不是证书固定(可适应证书轮换)
- 提供托管配置(MDM 配置文件或应用配置),以便在企业环境中禁用固定机制
- 至少固定 2 个密钥(当前密钥 + 备用密钥),以应对密钥轮换
密码套件
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
性能优化
用户空间网络(WWDC 2018 第 409–441 行)
在 iOS/tvOS 上自动启用。 Network.framework 会将 TCP/UDP 协议栈移入应用进程。
优势
- CPU 使用率降低约 30%(使用 Instruments 测量)
- 无需内核到用户空间的拷贝(内存映射区域)
- 减少上下文切换
传统网络与用户空间网络
| 传统套接字 | 用户空间网络 |
|---|---|
| 数据包 → 驱动程序 → 内核 → 拷贝 → 用户空间 | 数据包 → 驱动程序 → 内存映射区域 → 用户空间(无拷贝) |
| 100 个数据报 = 100 次系统调用 | 100 个数据报 = 约 1 次系统调用(使用批处理) |
| CPU 使用率高约 30% | 基准 CPU 使用率 |
WWDC 演示:实时 UDP 视频流展示了 30% 的 CPU 使用率差异(套接字与 Network.framework)。
UDP 的 ECN(WWDC 2018 第 365–378 行)
显式拥塞通知,可实现平稳的 UDP 传输。
// 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 标志
.ect0/.ect1— 支持 ECN 的传输.congestionEncountered— 收到拥塞通知
优势:网络可以在不丢弃数据包的情况下发出拥塞信号。
服务类别(WWDC 2018 第 379–388 行)
标记流量优先级。
// 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— 低优先级(大型下载、同步).default— 普通优先级.responsiveData— 交互式数据(API 调用).realTimeInteractive— 对时间敏感(语音、游戏)
TCP 快速打开(WWDC 2018 第 389–406 行)
在 TCP SYN 数据包中发送初始数据(节省一次往返)。
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)
优势:将连接建立时间缩短 1 RTT。 要求:数据必须是幂等的(如果 SYN 重传,重复发送也安全)。
迁移策略
从 BSD 套接字迁移到 NWConnection
| BSD 套接字 | NWConnection | 说明 |
|---|---|---|
socket() + connect() | NWConnection(host:port:using:) + start() | 默认非阻塞 |
send() / sendto() | connection.send(content:completion:) | 异步回调 |
recv() / recvfrom() | connection.receive(min:max:completion:) | 异步回调 |
bind() + listen() | NWListener(using:on:) | 自动绑定端口 |
accept() | listener.newConnectionHandler | 每个连接都会触发回调 |
getaddrinfo() | 使用 NWEndpoint.Host(hostname) | 自动进行 DNS 解析 |
SCNetworkReachability | connection.stateUpdateHandler 等待状态 | 无竞态条件 |
setsockopt() | NWParameters | 类型安全的选项 |
迁移示例
之前(阻塞式套接字)
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &addr, addrlen); // BLOCKS
send(sock, data, len, 0);
之后(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)
从 URLSession StreamTask 迁移到 NetworkConnection
适合迁移的情况
- 需要 UDP(StreamTask 仅支持 TCP)
- 需要自定义协议
- 需要底层控制
适合继续使用 URLSession 的情况
- HTTP/HTTPS(URLSession 针对此进行了优化)
- 需要 WebSocket 支持
- 需要内置缓存、Cookie
迁移示例
之前(URLSession StreamTask)
let task = URLSession.shared.streamTask(withHostName: "example.com", port: 443)
task.resume()
task.write(Data("Hello".utf8), timeout: 10) { _ in }
之后(NetworkConnection iOS 26+)
let connection = NetworkConnection(to: .hostPort(host: "example.com", port: 443)) { TLS() }
try await connection.send(Data("Hello".utf8))
从 NWConnection 迁移到 NetworkConnection
迁移优势
- Async/await(无需嵌套回调)
- 无需
[weak self] - 内置 TLV 分帧
- 提供适用于 Codable 类型的 Coder 协议
迁移映射
| 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:) |
| 手动处理 JSON | Coder(MyType.self, using: .json) |
| 自定义分帧器 | TLV { TLS() } |
到处使用 [weak self] | 无需使用 [weak self] |
迁移示例
之前(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()
})
}
之后(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
}
}
}
测试清单
发布网络代码前:
设备测试
- 在真实设备上测试(不只是模拟器)
- 在多个 iOS 版本上测试(12、15、26)
- 在 iPhone 和 iPad 上测试(网络特性各不相同)
网络状况
- Wi-Fi(家庭网络)
- 蜂窝网络(关闭 Wi-Fi)
- 飞行模式 → Wi-Fi(测试等待状态)
- Wi-Fi → 蜂窝网络切换(走出建筑物)
- 蜂窝网络 → Wi-Fi 切换(走进建筑物)
- 信号较弱(地下室、电梯)
- Network Link Conditioner(100ms 延迟、3% 丢包率)
网络类型
- 仅 IPv4 网络
- 仅 IPv6 网络(部分蜂窝运营商)
- 双栈(IPv4 + IPv6)
- 已启用企业 VPN
- 个人热点(高成本路径)
性能
- 建立连接耗时 < 500ms(检查日志)
- 对 UDP 使用批处理(使用 Instruments 验证)
- 使用 contentProcessed 控制发送节奏(检查发送时序)
- 使用 Instruments Network 模板进行性能分析
- CPU 使用率可接受(网络处理低于 10%)
- 内存稳定(无泄漏,检查 [weak self])
错误处理
- 处理 .waiting 状态(显示“正在等待……”界面)
- 处理 .failed 状态(显示具体错误消息)
- 记录 TLS 握手错误
- 处理超时(不要无限期等待)
- 面向用户的错误应便于采取行动(“检查网络”,而非“POSIX 61”)
iOS 26+ 功能(如使用 NetworkConnection)
- 如需消息边界,使用 TLV 分帧
- 如发送 Codable 类型,使用 Coder 协议
- 使用 NetworkListener 取代 NWListener
- 如需点对点连接,使用 NetworkBrowser 实现 Wi-Fi Aware
API 速查
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)
资源
WWDC:2018-715、2025-250
文档:/network、/network/nwconnection、/network/networkconnection
技能:axiom-networking、axiom-networking-diag
最后更新 2025-12-02
状态 基于 WWDC 2018 和 WWDC 2025 的生产就绪参考资料
覆盖范围 NWConnection(iOS 12-25)、NetworkConnection(iOS 26+)、WWDC 2025 的全部 12 个代码示例