mcpField
A
axiom-network-framework-ref

axiom-network-framework-ref

github.com/charleswiltgen开发1.2k170 次安装今天更新
代码仓库

关于 axiom-network-framework-ref

面向 Apple 平台开发者的 Network.framework API 参考技能,涵盖从 NWConnection 到 iOS 26 NetworkConnection 的用法与迁移,并提供连接、消息分帧和设备发现示例。

主要功能

  1. 01对比 NWConnection 与 NetworkConnection
  2. 02配置 TCP、UDP、TLS 和 QUIC 连接
  3. 03使用 async/await 发送和接收数据
  4. 04通过 TLV 与 Coder 处理消息
  5. 05设置监听器及 Bonjour、Wi-Fi Aware 发现

适用场景

  1. 01将 BSD 套接字或旧 API 迁移至 Network.framework
  2. 02为自定义协议选择传输方式
  3. 03实现点对点通信或设备发现
  4. 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 版本主要功能
2018iOS 12引入 NWConnection、NWListener、NWBrowser
2019iOS 13用户空间网络(CPU 占用降低 30%),默认使用 TLS 1.3
2021iOS 15URLSession 支持 WebSocket
2025iOS 26NetworkConnection(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 解析
SCNetworkReachabilityconnection.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 协议
迁移映射
NWConnectionNetworkConnection
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:)
手动处理 JSONCoder(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 个代码示例

评论

支持 Markdown