文章

基于 Apple Network Framework 的现代网络开发(iOS / macOS 26)

基于 Apple Network Framework 的现代网络开发(iOS / macOS 26)

Apple 的 Network 框架已经发展为一个统一的、对 Swift 友好的工具包,用于在应用中构建安全、高性能的网络功能——无需 BSD 套接字的样板代码和陷阱。本文总结了 iOS 和 macOS 26 引入的新 API 与模式,提供完整代码示例,并解释它们如何与 Swift 的结构化并发模型相契合。


1. 为什么选择 Network Framework?

  • 按名称连接 & Happy Eyeballs – 自动 DNS 解析与智能 IPv4/IPv6 竞速。
  • 内置 TLS – 无需外部 SSL 库即可实现端到端加密。
  • 移动性与代理支持 – 无缝的网络接口切换(如 Wi-Fi Assist、多路径)。
  • 现代传输协议 – 原生支持 QUIC,此外还有 TCP/UDP。
  • 可组合协议栈 – 以声明式方式叠加 TLS、TLV、Coder 等。
  • WebSocket 支持 – 一个服务端可同时服务原生与 Web 应用。
  • 结构化并发 – 所有 API 都支持 async/await,并可取消任务。

2. 创建连接

2.1 最小化 TLS 连接

1
2
3
4
5
import Network

let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
    TLS()
}
  • Endpoint 描述 连接的目标
  • Protocol stack 描述 如何连接
  • Parameters 则细化连接的 条件

2.2 添加 TCP 与 IP 选项

1
2
3
4
5
6
7
8
9
10
import Network

let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
    TLS {
        TCP {
            IP()
                .fragmentationEnabled(false)
        }
    }
}

TCPIP 默认会被推断,仅在需要自定义选项时显式声明(如禁用分片)。


2.3 自定义连接参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Network

let connection = NetworkConnection(
    to: .hostPort(host: "www.example.com", port: 1029),
    using: .parameters {
        TLS {
            TCP {
                IP()
                    .fragmentationEnabled(false)
            }
        }
    }
    .constrainedPathsProhibited(true)   // 遵循低数据模式
)

通过显式提供 NWParameters,可以在低数据模式下禁止使用受限网络接口。


3. 理解连接生命周期

NetworkConnection 会自动经历以下状态:

状态说明
preparingDNS 解析、Happy Eyeballs 竞速、协议握手
ready连接建立完成,可以安全收发数据
waiting暂时没有可用路径(如断网)
failed不可恢复错误(会附带 Error
cancelled任务或应用显式取消

可忽略状态,直接在 send/receive 时等待就绪;也可以监听状态更新,用于驱动 UI 提示。


4. 数据收发

4.1 简单的流式传输

1
2
3
4
5
6
7
8
9
10
11
12
13
import Network

public func sendAndReceiveWithTLS() async throws {
    let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        TLS()
    }

    let outgoingData = Data("Hello, world!".utf8)
    try await connection.send(outgoingData)

    let incomingData = try await connection.receive(exactly: 98).content
    print("Received data: \(incomingData)")
}

send 会挂起,直到数据排入发送队列。 receive(exactly:) 会等待直到接收到指定字节数。


4.2 解析变长负载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Network

public func sendAndReceiveWithTLS() async throws {
    let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        TLS()
    }

    let outgoingData = Data("Hello, world!".utf8)
    try await connection.send(outgoingData)

    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
        // 增量解码图像块
    }
}

由于流协议不保证消息边界,需要手动定义消息帧(如长度+负载)。


5. 使用 TLV 进行消息分帧

内置的 Type-Length-Value (TLV) 分帧器保证发送的消息在接收端完整对应。

5.1 数据结构

1
2
3
4
5
6
7
8
9
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 }

5.2 发送

1
2
3
4
5
6
7
8
9
10
11
12
import Network

public func sendWithTLV() async throws {
    let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        TLV {      
            TLS()
        }
    }

    let characterData = try JSONEncoder().encode(GameCharacter(character: "🐨"))
    try await connection.send(characterData, type: GameMessage.selectedCharacter.rawValue)
}

5.3 接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Network

public func receiveWithTLV() async throws {
    let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        TLV {
            TLS()
        }
    }

    let (data, meta) = try await connection.receive()
    switch GameMessage(rawValue: meta.type) {
    case .selectedCharacter:
        let c = try JSONDecoder().decode(GameCharacter.self, from: data)
        print("角色选择: \(c)")
    case .move:
        let m = try JSONDecoder().decode(GameMove.self, from: data)
        print("移动: \(m)")
    case .none:
        print("未知消息")
    }
}

6. 使用 Coder 实现零样板序列化

在协议栈中加入 Coder,即可直接收发 Codable 类型,无需手动编解码。

6.1 统一枚举

1
2
3
4
5
6
import Network

enum GameMessage: Codable {
    case selectedCharacter(String)
    case move(row: Int, column: Int)
}

6.2 发送

1
2
3
4
5
6
7
8
9
10
11
import Network

public func sendWithCoder() async throws {
    let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        Coder(GameMessage.self, using: .json) {  
            TLS()
        }
    }

    try await connection.send(GameMessage.selectedCharacter("🐨"))
}

6.3 接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Network

public func receiveWithCoder() async throws {
    let connection = NetworkConnection(to: .hostPort(host: "www.example.com", port: 1029)) {
        Coder(GameMessage.self, using: .json) {
            TLS()
        }
    }

    let message = try await connection.receive().content
    switch message {
    case .selectedCharacter(let char):
        print("角色选择: \(char)")
    case .move(let row, let col):
        print("移动: (\(row), \(col))")
    }
}

Coder 自动处理分帧、序列化与反序列化,大幅减少自定义网络代码。


7. 监听传入连接

NetworkListener 提供高层级的“服务端套接字”抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Network

public func listenForIncomingConnections() async throws {
    try await NetworkListener {
        Coder(GameMessage.self, using: .json) {
            TLS()
        }
    }
    .run { connection in
        for try await (message, _) in connection.messages {
            // 处理每条消息
        }
    }
}
  • run 为每个连接生成一个新的 Task
  • 监听器在父任务取消时自动结束。

8. 使用 NetworkBrowser 发现对等端

通过 NetworkBrowser 可以动态发现端点——包括 Bonjour 与新引入的 Wi-Fi Aware 技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
import Network
import WiFiAware

public func findNearbyDevice() async throws {
    let endpoint = try await NetworkBrowser(
        for: .wifiAware(.connecting(to: .allPairedDevices,
                                    from: .ticTacToeService)))
    .run { endpoints in
        .finish(endpoints.first!)
    }

    // 使用 `endpoint` 创建 NetworkConnection
}

NetworkBrowser 只负责发现,真正的协议栈仍在创建 NetworkConnection 时指定。


9. 如何选择合适的协议栈

场景推荐协议栈
与现有服务器通信遵循服务端要求(如 IPP over TCP)
自家应用/设备间通信Coder ➜ TLSCoder ➜ QUIC
HTTP/HTTPS使用 URLSession,无需修改代码

10. 总结

  • 新核心类型NetworkConnectionNetworkListenerNetworkBrowser
  • 分帧选项 – 内置 TLV;Coder 支持 Codable 类型。
  • Swift 并发支持 – 声明式构建器、async API、自动任务取消。
  • 安全与传输组合 – TLS、QUIC、多路径、代理,全部无缝支持。
  • 动态发现 – Bonjour 与跨平台 Wi-Fi Aware。

这些 API 让现代、安全、高效的网络应用开发更加简单。通过拥抱结构化并发、声明式协议栈与一流安全性,开发者可以专注于业务逻辑与用户体验——而连接管理、消息分帧与序列化交由 Network 框架处理。

本文由作者按照 CC BY 4.0 进行授权

热门标签