axiom-networking
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNetwork.framework Networking
Network.framework 网络编程
When to Use This Skill
何时使用本技能
Use when:
- Implementing UDP/TCP connections for gaming, streaming, or messaging apps
- Migrating from BSD sockets, CFSocket, NSStream, or SCNetworkReachability
- Debugging connection timeouts or TLS handshake failures
- Supporting network transitions (WiFi ↔ cellular) gracefully
- Adopting structured concurrency networking patterns (iOS 26+)
- Implementing custom protocols over TLS/QUIC
- Requesting code review of networking implementation before shipping
适用于以下场景:
- 为游戏、流媒体或消息类应用实现UDP/TCP连接
- 从BSD sockets、CFSocket、NSStream或SCNetworkReachability迁移至新方案
- 调试连接超时或TLS握手失败问题
- 优雅支持网络切换(WiFi ↔ 蜂窝网络)
- 采用结构化并发网络模式(iOS 26+)
- 在TLS/QUIC之上实现自定义协议
- 上线前对网络实现代码进行评审
Related Skills
相关技能
- Use for systematic troubleshooting of connection failures, timeouts, and performance issues
axiom-networking-diag - Use for comprehensive API reference with all WWDC examples
axiom-network-framework-ref
- 使用对连接失败、超时和性能问题进行系统化排查
axiom-networking-diag - 使用获取包含所有WWDC示例的完整API参考文档
axiom-network-framework-ref
Example Prompts
示例提问
1. "How do I migrate from SCNetworkReachability? My app checks connectivity before connecting."
1. "我该如何从SCNetworkReachability迁移?我的应用会在连接前检查网络连通性。"
2. "My connection times out after 60 seconds. How do I debug this?"
2. "我的连接在60秒后超时,该如何调试?"
3. "Should I use NWConnection or NetworkConnection? What's the difference?"
3. "我应该用NWConnection还是NetworkConnection?两者有什么区别?"
Red Flags — Anti-Patterns to Prevent
需规避的反模式
If you're doing ANY of these, STOP and use the patterns in this skill:
如果你正在做以下任何操作,请立即停止,改用本技能中的规范模式:
❌ CRITICAL — Never Do These
❌ 严重错误——绝对不要这样做
1. Using SCNetworkReachability to check connectivity before connecting
1. 在连接前使用SCNetworkReachability检查连通性
swift
// ❌ WRONG — Race condition
if SCNetworkReachabilityGetFlags(reachability, &flags) {
connection.start() // Network may change between check and start
}Why this fails Network state changes between reachability check and connect(). You miss Network.framework's smart connection establishment (Happy Eyeballs, proxy handling, WiFi Assist). Apple deprecated this API in 2018.
swift
// ❌ 错误——存在竞态条件
if SCNetworkReachabilityGetFlags(reachability, &flags) {
connection.start() // 检查与启动之间网络状态可能已变化
}失败原因 网络状态会在可达性检查与调用connect()之间发生变化。你会错过Network.framework的智能连接建立机制(Happy Eyeballs、代理处理、WiFi Assist)。Apple在2018年已废弃该API。
2. Blocking socket operations on main thread
2. 在主线程执行阻塞式socket操作
swift
// ❌ WRONG — Guaranteed ANR (Application Not Responding)
let socket = socket(AF_INET, SOCK_STREAM, 0)
connect(socket, &addr, addrlen) // Blocks main threadWhy this fails Main thread hang → frozen UI → App Store rejection for responsiveness. Even "quick" connects take 200-500ms.
swift
// ❌ 错误——必然导致ANR(应用无响应)
let socket = socket(AF_INET, SOCK_STREAM, 0)
connect(socket, &addr, addrlen) // 阻塞主线程失败原因 主线程挂起会导致UI冻结,进而因响应性问题被App Store拒绝。即使是“快速”连接也需要200-500ms。
3. Manual DNS resolution with getaddrinfo
3. 使用getaddrinfo手动进行DNS解析
swift
// ❌ WRONG — Misses Happy Eyeballs, proxies, VPN
var hints = addrinfo(...)
getaddrinfo("example.com", "443", &hints, &results)
// Now manually try each address...Why this fails You reimplement 10+ years of Apple's connection logic poorly. Misses IPv4/IPv6 racing, proxy evaluation, VPN detection.
swift
// ❌ 错误——错过Happy Eyeballs、代理、VPN支持
var hints = addrinfo(...)
getaddrinfo("example.com", "443", &hints, &results)
// 现在手动尝试每个地址...失败原因 你在拙劣地重写Apple耗时10多年打磨的连接逻辑,会错过IPv4/IPv6竞速、代理评估、VPN检测等功能。
4. Hardcoded IP addresses instead of hostnames
4. 使用硬编码IP地址而非主机名
swift
// ❌ WRONG — Breaks proxy/VPN compatibility
let host = "192.168.1.1" // or any IP literalWhy this fails Proxy auto-configuration (PAC) needs hostname to evaluate rules. VPNs can't route properly. DNS-based load balancing broken.
swift
// ❌ 错误——破坏代理/VPN兼容性
let host = "192.168.1.1" // 或任何IP字面量失败原因 代理自动配置(PAC)需要主机名来评估规则,VPN无法正确路由,基于DNS的负载均衡也会失效。
5. Ignoring waiting state — not handling lack of connectivity
5. 忽略等待状态——未处理无网络连通性的情况
swift
// ❌ WRONG — Poor UX
connection.stateUpdateHandler = { state in
if case .ready = state {
// Handle ready
}
// Missing: .waiting case
}Why this fails User sees "Connection failed" in Airplane Mode instead of "Waiting for network." No automatic retry when WiFi returns.
swift
// ❌ 错误——用户体验差
connection.stateUpdateHandler = { state in
if case .ready = state {
// 处理就绪状态
}
// 缺失:.waiting状态处理
}失败原因 飞行模式下用户会看到“连接失败”,而非“等待网络连接”。WiFi恢复时也不会自动重试。
6. Not using [weak self] in NWConnection completion handlers
6. 在NWConnection完成回调中未使用[weak self]
swift
// ❌ WRONG — Memory leak
connection.send(content: data, completion: .contentProcessed { error in
self.handleSend(error) // Retain cycle: connection → handler → self → connection
})Why this fails Connection retains completion handler, handler captures self strongly, self retains connection → memory leak.
swift
// ❌ 错误——内存泄漏
connection.send(content: data, completion: .contentProcessed { error in
self.handleSend(error) // 循环引用:connection → handler → self → connection
})失败原因 Connection持有完成回调,回调强引用self,self又持有connection,最终导致内存泄漏。
7. Mixing async/await and completion handlers in NetworkConnection (iOS 26+)
7. 在NetworkConnection中混合使用async/await和完成回调(iOS 26+)
swift
// ❌ WRONG — Structured concurrency violation
Task {
let connection = NetworkConnection(...)
connection.send(data) // async/await
connection.stateUpdateHandler = { ... } // completion handler — don't mix
}Why this fails NetworkConnection designed for pure async/await. Mixing paradigms creates difficult error propagation and cancellation issues.
swift
// ❌ 错误——违反结构化并发规范
Task {
let connection = NetworkConnection(...)
connection.send(data) // async/await
connection.stateUpdateHandler = { ... } // 完成回调——不要混合使用
}失败原因 NetworkConnection专为纯async/await设计,混合两种范式会导致难以处理的错误传播和取消问题。
8. Not supporting network transitions
8. 未支持网络切换
swift
// ❌ WRONG — Connection fails on WiFi → cellular transition
// No viabilityUpdateHandler, no betterPathUpdateHandler
// User walks out of building → connection diesWhy this fails Modern apps must handle network changes gracefully. 40% of connection failures happen during network transitions.
swift
// ❌ 错误——WiFi切换至蜂窝网络时连接失败
// 未使用viabilityUpdateHandler或betterPathUpdateHandler
// 用户走出建筑后连接中断失败原因 现代应用必须优雅处理网络变化,40%的连接失败发生在网络切换过程中。
Mandatory First Steps
必须完成的初始步骤
ALWAYS complete these steps before writing any networking code:
swift
// Step 1: Identify your use case
// Record: "UDP gaming" vs "TLS messaging" vs "Custom protocol over QUIC"
// Ask: What data am I sending? Real-time? Reliable delivery needed?
// Step 2: Check if URLSession is sufficient
// URLSession handles: HTTP, HTTPS, WebSocket, TCP/TLS streams (via StreamTask)
// Network.framework handles: UDP, custom protocols, low-level control, peer-to-peer
// If HTTP/HTTPS/WebSocket → STOP, use URLSession instead
// Example:
URLSession.shared.dataTask(with: url) { ... } // ✅ Correct for HTTP
// Step 3: Choose API version based on deployment target
if #available(iOS 26, *) {
// Use NetworkConnection (structured concurrency, async/await)
// TLV framing built-in, Coder protocol for Codable types
} else {
// Use NWConnection (completion handlers)
// Manual framing or custom framers
}
// Step 4: Verify you're NOT using deprecated APIs
// Search your codebase for these:
// - SCNetworkReachability → Use connection waiting state
// - CFSocket → Use NWConnection
// - NSStream, CFStream → Use NWConnection
// - NSNetService → Use NWBrowser or NetworkBrowser
// - getaddrinfo → Let Network.framework handle DNS
// To search:
// grep -rn "SCNetworkReachability\|CFSocket\|NSStream\|getaddrinfo" .在编写任何网络代码前,请务必完成以下步骤:
swift
// 步骤1:明确你的使用场景
// 记录:“UDP游戏” vs “TLS消息” vs “基于QUIC的自定义协议”
// 思考:我要发送什么数据?是否需要实时传输?是否需要可靠交付?
// 步骤2:检查URLSession是否能满足需求
// URLSession支持:HTTP、HTTPS、WebSocket、TCP/TLS流(通过StreamTask)
// Network.framework支持:UDP、自定义协议、底层控制、点对点通信
// 如果是HTTP/HTTPS/WebSocket → 停止,使用URLSession
// 示例:
URLSession.shared.dataTask(with: url) { ... } // ✅ HTTP场景下的正确选择
// 步骤3:根据部署目标选择API版本
if #available(iOS 26, *) {
// 使用NetworkConnection(结构化并发、async/await)
// 内置TLV帧,支持Codable类型的Coder协议
} else {
// 使用NWConnection(完成回调)
// 需要手动处理帧或自定义帧处理器
}
// 步骤4:确认未使用废弃API
// 在代码库中搜索以下内容:
// - SCNetworkReachability → 使用connection的waiting状态替代
// - CFSocket → 使用NWConnection
// - NSStream、CFStream → 使用NWConnection
// - NSNetService → 使用NWBrowser或NetworkBrowser
// - getaddrinfo → 让Network.framework处理DNS
// 搜索命令:
// grep -rn "SCNetworkReachability\|CFSocket\|NSStream\|getaddrinfo" .What this tells you
这些步骤的作用
- If HTTP/HTTPS: Use URLSession, not Network.framework
- If iOS 26+ deployment: Use NetworkConnection with async/await
- If iOS 12-25 support needed: Use NWConnection with completion handlers
- If any deprecated API found: Must migrate before shipping (App Store review concern)
- 如果是HTTP/HTTPS场景:使用URLSession,而非Network.framework
- 如果部署目标为iOS 26+:使用带async/await的NetworkConnection
- 如果需要支持iOS 12-25:使用带完成回调的NWConnection
- 如果发现任何废弃API:上线前必须迁移(App Store审核关注点)
Decision Tree
决策树
Use this to select the correct pattern in 2 minutes:
Need networking?
├─ HTTP, HTTPS, or WebSocket?
│ └─ YES → Use URLSession (NOT Network.framework)
│ ✅ URLSession.shared.dataTask(with: url)
│ ✅ URLSession.webSocketTask(with: url)
│ ✅ URLSession.streamTask(withHostName:port:) for TCP/TLS
│
├─ iOS 26+ and can use structured concurrency?
│ └─ YES → NetworkConnection path (async/await)
│ ├─ TCP with TLS security?
│ │ └─ Pattern 1a: NetworkConnection + TLS
│ │ Time: 10-15 minutes
│ │
│ ├─ UDP for gaming/streaming?
│ │ └─ Pattern 1b: NetworkConnection + UDP
│ │ Time: 10-15 minutes
│ │
│ ├─ Need message boundaries (framing)?
│ │ └─ Pattern 1c: TLV Framing
│ │ Type-Length-Value for mixed message types
│ │ Time: 15-20 minutes
│ │
│ └─ Send/receive Codable objects directly?
│ └─ Pattern 1d: Coder Protocol
│ No manual JSON encoding needed
│ Time: 10-15 minutes
│
└─ iOS 12-25 or need completion handlers?
└─ YES → NWConnection path (callbacks)
├─ TCP with TLS security?
│ └─ Pattern 2a: NWConnection + TLS
│ stateUpdateHandler, completion-based send/receive
│ Time: 15-20 minutes
│
├─ UDP streaming with batching?
│ └─ Pattern 2b: NWConnection + UDP Batch
│ connection.batch for 30% CPU reduction
│ Time: 10-15 minutes
│
├─ Listening for incoming connections?
│ └─ Pattern 2c: NWListener
│ Accept inbound connections, newConnectionHandler
│ Time: 20-25 minutes
│
└─ Network discovery (Bonjour)?
└─ Pattern 2d: NWBrowser
Discover services on local network
Time: 25-30 minutes使用以下决策树,2分钟内选择正确的实现模式:
需要网络功能?
├─ 是否为HTTP、HTTPS或WebSocket?
│ └─ 是 → 使用URLSession(不要用Network.framework)
│ ✅ URLSession.shared.dataTask(with: url)
│ ✅ URLSession.webSocketTask(with: url)
│ ✅ URLSession.streamTask(withHostName:port:) 用于TCP/TLS
│
├─ 是否为iOS 26+且可使用结构化并发?
│ └─ 是 → 选择NetworkConnection方案(async/await)
│ ├─ 是否需要带TLS安全的TCP?
│ │ └─ 模式1a:NetworkConnection + TLS
│ │ 耗时:10-15分钟
│ │
│ ├─ 是否为游戏/流媒体使用UDP?
│ │ └─ 模式1b:NetworkConnection + UDP
│ │ 耗时:10-15分钟
│ │
│ ├─ 是否需要消息边界(帧处理)?
│ │ └─ 模式1c:TLV帧处理
│ │ 用于混合消息类型的Type-Length-Value格式
│ │ 耗时:15-20分钟
│ │
│ └─ 是否需要直接发送/接收Codable对象?
│ └─ 模式1d:Coder协议
│ 无需手动JSON编码
│ 耗时:10-15分钟
│
└─ 是否为iOS 12-25或需要使用完成回调?
└─ 是 → 选择NWConnection方案(回调)
├─ 是否需要带TLS安全的TCP?
│ └─ 模式2a:NWConnection + TLS
│ 使用stateUpdateHandler、基于完成回调的发送/接收
│ 耗时:15-20分钟
│
├─ 是否为批量UDP流?
│ └─ 模式2b:NWConnection + UDP Batch
│ 使用connection.batch可降低30% CPU占用
│ 耗时:10-15分钟
│
├─ 是否需要监听 incoming 连接?
│ └─ 模式2c:NWListener
│ 接受入站连接,使用newConnectionHandler
│ 耗时:20-25分钟
│
└─ 是否需要网络发现(Bonjour)?
└─ 模式2d:NWBrowser
发现本地网络中的服务
耗时:25-30分钟Quick selection guide
快速选择指南
- Gaming (low latency, some loss OK) → UDP patterns (1b or 2b)
- Messaging (reliable, ordered) → TLS patterns (1a or 2a)
- Mixed message types → TLV or Coder (1c or 1d)
- Peer-to-peer → Discovery patterns (2d) + incoming (2c)
- 游戏(低延迟、允许部分丢包)→ UDP模式(1b或2b)
- 消息(可靠、有序)→ TLS模式(1a或2a)
- 混合消息类型 → TLV或Coder模式(1c或1d)
- 点对点通信 → 发现模式(2d)+ 入站连接模式(2c)
Common Patterns
常见实现模式
Pattern 1a: NetworkConnection with TLS (iOS 26+)
模式1a:搭配TLS的NetworkConnection(iOS 26+)
Use when iOS 26+ deployment, need reliable TCP with TLS security, want async/await
Time cost 10-15 minutes
适用场景 部署目标为iOS 26+,需要带TLS安全的可靠TCP连接,希望使用async/await
耗时 10-15分钟
❌ BAD: Manual DNS, Blocking Socket
❌ 错误示例:手动DNS、阻塞式Socket
swift
// WRONG — Don't do this
var hints = addrinfo(...)
getaddrinfo("www.example.com", "1029", &hints, &results)
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, results.pointee.ai_addr, results.pointee.ai_addrlen) // Blocks!swift
// 错误——不要这样做
var hints = addrinfo(...)
getaddrinfo("www.example.com", "1029", &hints, &results)
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, results.pointee.ai_addr, results.pointee.ai_addrlen) // 阻塞!✅ GOOD: NetworkConnection with Declarative Stack
✅ 正确示例:带声明式栈的NetworkConnection
swift
import Network
// Basic connection with TLS
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
TLS() // TCP and IP inferred automatically
}
// 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)")
}
// Optional: Monitor connection state for UI updates
Task {
for await state in connection.states {
switch state {
case .preparing:
print("Establishing connection...")
case .ready:
print("Connected!")
case .waiting(let error):
print("Waiting for network: \(error)")
case .failed(let error):
print("Connection failed: \(error)")
case .cancelled:
print("Connection cancelled")
@unknown default:
break
}
}
}swift
import Network
// 基础TLS连接
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
TLS() // 自动推断TCP和IP
}
// 使用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)")
}
// 可选:监听连接状态用于UI更新
Task {
for await state in connection.states {
switch state {
case .preparing:
print("Establishing connection...")
case .ready:
print("Connected!")
case .waiting(let error):
print("Waiting for network: \(error)")
case .failed(let error):
print("Connection failed: \(error)")
case .cancelled:
print("Connection cancelled")
@unknown default:
break
}
}
}Custom parameters for low data mode
低数据模式的自定义参数
swift
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
)swift
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029),
using: .parameters {
TLS {
TCP {
IP()
.fragmentationEnabled(false)
}
}
}
.constrainedPathsProhibited(true) // 低数据模式下不使用蜂窝网络
)When to use
适用场景
- Secure messaging, email protocols (IMAP, SMTP)
- Custom protocols requiring encryption
- APIs using non-HTTP protocols
- 安全消息、邮件协议(IMAP、SMTP)
- 需要加密的自定义协议
- 使用非HTTP协议的API
Performance characteristics
性能特点
- Smart connection establishment: Happy Eyeballs (IPv4/IPv6 racing), proxy evaluation, VPN detection
- TLS 1.3 by default (faster handshake)
- User-space networking: ~30% lower CPU usage vs sockets
- 智能连接建立:支持Happy Eyeballs(IPv4/IPv6竞速)、代理评估、VPN检测
- 默认使用TLS 1.3(握手更快)
- 用户态网络:相比sockets降低约30% CPU占用
Debugging
调试方法
- Enable logging:
-NWLoggingEnabled 1 -NWConnectionLoggingEnabled 1 - Check connection.states async sequence for state transitions
- Test on real device with Airplane Mode toggle
- 启用日志:
-NWLoggingEnabled 1 -NWConnectionLoggingEnabled 1 - 监听connection.states异步序列获取状态变化
- 在真实设备上测试飞行模式切换
Pattern 1b: NetworkConnection UDP (iOS 26+)
模式1b:搭配UDP的NetworkConnection(iOS 26+)
Use when iOS 26+ deployment, need UDP datagrams for gaming or real-time streaming, want async/await
Time cost 10-15 minutes
适用场景 部署目标为iOS 26+,需要为游戏或实时流使用UDP数据报,希望使用async/await
耗时 10-15分钟
❌ BAD: Blocking UDP Socket
❌ 错误示例:阻塞式UDP Socket
swift
// WRONG — Don't do this
let sock = socket(AF_INET, SOCK_DGRAM, 0)
let sent = sendto(sock, buffer, length, 0, &addr, addrlen)
// Blocks, no batching, axiom-high CPU overheadswift
// 错误——不要这样做
let sock = socket(AF_INET, SOCK_DGRAM, 0)
let sent = sendto(sock, buffer, length, 0, &addr, addrlen)
// 阻塞、无批量处理、CPU开销高✅ GOOD: NetworkConnection with UDP
✅ 正确示例:搭配UDP的NetworkConnection
swift
import Network
// UDP connection for real-time data
let connection = NetworkConnection(
to: .hostPort(host: "game-server.example.com", port: 9000)
) {
UDP()
}
// Send game state update
public func sendGameUpdate() async throws {
let gameState = Data("player_position:100,50".utf8)
try await connection.send(gameState)
}
// Receive game updates
public func receiveGameUpdates() async throws {
while true {
let (data, _) = try await connection.receive()
processGameState(data)
}
}
// Batch multiple datagrams for efficiency (30% CPU reduction)
public func sendMultipleUpdates(_ updates: [Data]) async throws {
for update in updates {
try await connection.send(update)
}
}swift
import Network
// 用于实时数据的UDP连接
let connection = NetworkConnection(
to: .hostPort(host: "game-server.example.com", port: 9000)
) {
UDP()
}
// 发送游戏状态更新
public func sendGameUpdate() async throws {
let gameState = Data("player_position:100,50".utf8)
try await connection.send(gameState)
}
// 接收游戏更新
public func receiveGameUpdates() async throws {
while true {
let (data, _) = try await connection.receive()
processGameState(data)
}
}
// 批量发送多个数据报以提升效率(降低30% CPU占用)
public func sendMultipleUpdates(_ updates: [Data]) async throws {
for update in updates {
try await connection.send(update)
}
}When to use
适用场景
- Real-time gaming (player position, game state)
- Live streaming (video/audio frames where some loss is acceptable)
- IoT telemetry (sensor data)
- 实时游戏(玩家位置、游戏状态)
- 直播流(可接受部分丢包的视频/音频帧)
- IoT遥测(传感器数据)
Performance characteristics
性能特点
- User-space networking: ~30% lower CPU vs sockets
- Batching multiple sends reduces context switches
- ECN (Explicit Congestion Notification) enabled automatically
- 用户态网络:相比sockets降低约30% CPU占用
- 批量发送减少上下文切换
- 自动启用ECN(显式拥塞通知)
Debugging
调试方法
- Use Instruments Network template to profile datagram throughput
- Check for packet loss with receive timeouts
- Test on cellular network (higher latency/loss)
- 使用Instruments的Network模板分析数据报吞吐量
- 检查接收超时情况以排查丢包问题
- 在蜂窝网络下测试(更高延迟/丢包率)
Pattern 1c: TLV Framing (iOS 26+)
模式1c:TLV帧处理(iOS 26+)
Use when Need message boundaries on stream protocols (TCP/TLS), have mixed message types, want type-safe message handling
Time cost 15-20 minutes
Background Stream protocols (TCP/TLS) don't preserve message boundaries. If you send 3 chunks, receiver might get them 1 byte at a time, or all at once. TLV (Type-Length-Value) solves this by encoding each message with its type and length.
适用场景 流协议(TCP/TLS)下需要消息边界,存在混合消息类型,需要类型安全的消息处理
耗时 15-20分钟
背景 流协议(TCP/TLS)不保留消息边界。如果发送3个数据块,接收方可能逐字节接收,也可能一次性接收全部。TLV(Type-Length-Value)通过为每个消息编码类型和长度解决该问题。
❌ BAD: Manual Length Prefix Parsing
❌ 错误示例:手动长度前缀解析
swift
// WRONG — Error-prone, boilerplate-heavy
let lengthData = try await connection.receive(exactly: 4).content
let length = lengthData.withUnsafeBytes { $0.load(as: UInt32.self) }
let messageData = try await connection.receive(exactly: Int(length)).content
// Now decode manually...swift
// 错误——易出错、样板代码多
let lengthData = try await connection.receive(exactly: 4).content
let length = lengthData.withUnsafeBytes { $0.load(as: UInt32.self) }
let messageData = try await connection.receive(exactly: Int(length)).content
// 现在手动解码...✅ GOOD: TLV Framing with Type Safety
✅ 正确示例:带类型安全的TLV帧处理
swift
import Network
// Define your 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 messages
public func sendWithTLV() async throws {
let characterData = try JSONEncoder().encode(GameCharacter(character: "🐨"))
try await connection.send(characterData, type: GameMessage.selectedCharacter.rawValue)
}
// Receive typed messages
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)")
}
}swift
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)")
}
}When to use
适用场景
- Mixed message types in same connection (chat + presence + typing indicators)
- Existing protocols using TLV (many custom protocols)
- Need message boundaries without heavy framing overhead
- 同一连接中存在混合消息类型(聊天+在线状态+输入提示)
- 现有协议使用TLV(许多自定义协议)
- 需要消息边界且不希望有过高的帧处理开销
How it works
工作原理
- Type: UInt32 message identifier (your enum raw value)
- Length: UInt32 message size (automatic)
- Value: Actual message bytes
- Type:UInt32消息标识符(你的枚举原始值)
- Length:UInt32消息大小(自动生成)
- Value:实际消息字节
Performance characteristics
性能特点
- Minimal overhead: 8 bytes per message (type + length)
- No manual parsing: Framework handles framing
- Type-safe: Compiler catches message type errors
- 开销极小:每条消息仅8字节(类型+长度)
- 无需手动解析:框架处理帧逻辑
- 类型安全:编译器可捕获消息类型错误
Pattern 1d: Coder Protocol (iOS 26+)
模式1d:Coder协议(iOS 26+)
Use when Sending/receiving Codable types, want to eliminate JSON boilerplate, need type-safe message handling
Time cost 10-15 minutes
Background Most apps manually encode Codable types to JSON, send bytes, receive bytes, decode JSON. Coder protocol eliminates this boilerplate by handling serialization automatically.
适用场景 发送/接收Codable类型,希望消除JSON样板代码,需要类型安全的消息处理
耗时 10-15分钟
背景 大多数应用手动将Codable类型编码为JSON、发送字节、接收字节、解码JSON。Coder协议通过自动处理序列化消除这些样板代码。
❌ BAD: Manual JSON Encoding/Decoding
❌ 错误示例:手动JSON编码/解码
swift
// WRONG — Boilerplate-heavy, error-prone
let encoder = JSONEncoder()
let data = try encoder.encode(message)
try await connection.send(data)
let receivedData = try await connection.receive().content
let decoder = JSONDecoder()
let message = try decoder.decode(GameMessage.self, from: receivedData)swift
// 错误——样板代码多、易出错
let encoder = JSONEncoder()
let data = try encoder.encode(message)
try await connection.send(data)
let receivedData = try await connection.receive().content
let decoder = JSONDecoder()
let message = try decoder.decode(GameMessage.self, from: receivedData)✅ GOOD: Coder Protocol for Direct Codable Send/Receive
✅ 正确示例:使用Coder协议直接发送/接收Codable类型
swift
import Network
// Define message types as Codable enum
enum GameMessage: Codable {
case selectedCharacter(String)
case move(row: Int, column: Int)
}
// Connection with Coder protocol
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
Coder(GameMessage.self, using: .json) {
TLS()
}
}
// Send Codable types directly
public func sendWithCoder() async throws {
let selectedCharacter: GameMessage = .selectedCharacter("🐨")
try await connection.send(selectedCharacter) // No encoding needed!
}
// Receive Codable types directly
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))")
}
}swift
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))")
}
}Supported formats
支持的格式
- — JSON encoding (most common, human-readable)
.json - — Property list encoding (smaller, faster)
.propertyList
- — JSON编码(最常用、人类可读)
.json - — 属性列表编码(体积更小、速度更快)
.propertyList
When to use
适用场景
- App-to-app communication (you control both ends)
- Prototyping (fastest time to working code)
- Type-safe protocols (compiler catches message structure changes)
- App间通信(你控制两端实现)
- 原型开发(最快实现可用代码)
- 类型安全协议(编译器可捕获消息结构变化)
When NOT to use
不适用场景
- Interoperating with non-Swift servers
- Need custom wire format
- Performance-critical (prefer TLV with manual encoding for control)
- 与非Swift服务器交互
- 需要自定义有线格式
- 性能关键场景(如需精确控制,优先选择带手动编码的TLV)
Benefits
优势
- No JSON boilerplate: ~50 lines → ~10 lines
- Type-safe: Compiler catches message structure changes
- Automatic framing: Handles message boundaries
- 无JSON样板代码:约50行代码缩减至10行
- 类型安全:编译器可捕获消息结构变化
- 自动帧处理:处理消息边界
Legacy iOS 12-25 Patterns
iOS 12-25 遗留模式
For apps supporting iOS 12-25 that can't use async/await yet, invoke :
/skill axiom-networking-legacy- Pattern 2a: NWConnection with TLS (completion handlers)
- Pattern 2b: NWConnection UDP Batch (30% CPU reduction)
- Pattern 2c: NWListener (accepting connections, Bonjour)
- Pattern 2d: Network Discovery (NWBrowser for service discovery)
对于仍需支持iOS 12-25且无法使用async/await的应用,请调用:
/skill axiom-networking-legacy- 模式2a:带TLS的NWConnection(完成回调)
- 模式2b:NWConnection UDP Batch(降低30% CPU占用)
- 模式2c:NWListener(接受连接、Bonjour)
- 模式2d:网络发现(用于服务发现的NWBrowser)
Pressure Scenarios
压力场景处理
Scenario 1: Reachability Race Condition Under App Store Deadline
场景1:App Store提交截止前的可达性竞态条件问题
Context
背景
You're 3 days from App Store submission. QA reports connection failures on cellular networks (15% failure rate). Your PM reviews the code and suggests: "Just add a reachability check before connecting. If there's no network, show an error immediately instead of timing out."
距离App Store提交还有3天,QA报告蜂窝网络下连接失败率为15%。你的PM查看代码后建议:“只需在连接前添加可达性检查。如果没有网络,立即显示错误而非等待超时。”
Pressure signals
压力信号
- ⏰ Deadline pressure "App Store deadline is Friday. We need this fixed by EOD Wednesday."
- 👔 Authority pressure PM (non-technical) suggesting specific implementation
- 💸 Sunk cost Already spent 2 hours debugging connection logs, found nothing obvious
- 📊 Customer impact "15% of users affected, mostly on cellular"
- ⏰ 截止日期压力 “App Store截止日期是周五,我们需要在周三下班前修复。”
- 👔 权威压力 非技术背景的PM提出具体实现建议
- 💸 沉没成本 已花费2小时调试连接日志,未发现明显问题
- 📊 用户影响 “15%的用户受影响,主要是蜂窝网络用户”
Rationalization trap
合理化陷阱
"SCNetworkReachability is Apple's API, it must be correct. I've seen it in Stack Overflow answers with 500+ upvotes. Adding a quick reachability check will fix the issue today, and I can refactor it properly after launch. The deadline is more important than perfect code right now."
“SCNetworkReachability是Apple的API,肯定是正确的。我在Stack Overflow上看到过500+赞的答案使用它。现在添加快速可达性检查就能解决问题,上线后再重构。截止日期比完美代码更重要。”
Why this fails
为何该方案会失败
-
Race condition Network state changes between reachability check and connection start. You check "WiFi available" at 10:00:00.000, but WiFi disconnects at 10:00:00.050, then you call connection.start() at 10:00:00.100. Connection fails, but reachability said it was available.
-
Misses smart connection establishment Network.framework tries multiple strategies (IPv4, IPv6, proxies, WiFi Assist fallback to cellular). SCNetworkReachability gives you "yes/no" but doesn't tell you which strategy will work.
-
Deprecated API Apple explicitly deprecated SCNetworkReachability in WWDC 2018. App Store Review may flag this as using legacy APIs.
-
Doesn't solve actual problem 15% cellular failures likely caused by not handling waiting state, not by absence of reachability check.
-
竞态条件 可达性检查与连接启动之间网络状态可能变化。你在10:00:00.000检查到“WiFi可用”,但WiFi在10:00:00.050断开,然后在10:00:00.100调用connection.start(),最终连接失败,但可达性检查显示可用。
-
错过智能连接建立 Network.framework尝试多种策略(IPv4、IPv6、代理、WiFi Assist fallback至蜂窝网络)。SCNetworkReachability仅给出“是/否”,但无法告诉你哪种策略可行。
-
废弃API Apple在WWDC 2018明确废弃SCNetworkReachability,App Store审核可能将其标记为使用遗留API。
-
未解决实际问题 15%的蜂窝网络失败可能是因为未处理等待状态,而非缺少可达性检查。
MANDATORY response
必须采用的解决方案
swift
// ❌ NEVER check reachability before connecting
/*
if SCNetworkReachabilityGetFlags(reachability, &flags) {
if flags.contains(.reachable) {
connection.start()
} else {
showError("No network") // RACE CONDITION
}
}
*/
// ✅ ALWAYS let Network.framework handle waiting state
let connection = NWConnection(
host: NWEndpoint.Host("api.example.com"),
port: NWEndpoint.Port(integerLiteral: 443),
using: .tls
)
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .preparing:
// Show: "Connecting..."
self?.showStatus("Connecting...")
case .ready:
// Connection established
self?.hideStatus()
self?.sendRequest()
case .waiting(let error):
// CRITICAL: Don't fail here, show "Waiting for network"
// Network.framework will automatically retry when network returns
print("Waiting for network: \(error)")
self?.showStatus("Waiting for network...")
// User walks out of elevator → WiFi returns → automatic retry
case .failed(let error):
// Only fail after framework exhausts all options
// (tried IPv4, IPv6, proxies, WiFi Assist, waited for network)
print("Connection failed: \(error)")
self?.showError("Connection failed. Please check your network.")
case .cancelled:
self?.hideStatus()
@unknown default:
break
}
}
connection.start(queue: .main)swift
// ❌ 绝对不要在连接前检查可达性
/*
if SCNetworkReachabilityGetFlags(reachability, &flags) {
if flags.contains(.reachable) {
connection.start()
} else {
showError("No network") // 竞态条件
}
}
*/
// ✅ 始终让Network.framework处理等待状态
let connection = NWConnection(
host: NWEndpoint.Host("api.example.com"),
port: NWEndpoint.Port(integerLiteral: 443),
using: .tls
)
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .preparing:
// 显示:“连接中...”
self?.showStatus("Connecting...")
case .ready:
// 连接建立
self?.hideStatus()
self?.sendRequest()
case .waiting(let error):
// 关键:不要在此处失败,显示“等待网络连接”
// 网络恢复时Network.framework会自动重试
print("Waiting for network: \(error)")
self?.showStatus("Waiting for network...")
// 用户走出电梯后WiFi恢复,自动重试
case .failed(let error):
// 仅当框架尝试所有选项后再失败
// (已尝试IPv4、IPv6、代理、WiFi Assist、等待网络)
print("Connection failed: \(error)")
self?.showError("连接失败,请检查网络。")
case .cancelled:
self?.hideStatus()
@unknown default:
break
}
}
connection.start(queue: .main)Professional push-back template
专业反驳模板
"I understand the deadline pressure. However, adding SCNetworkReachability will create a race condition that will make the 15% failure rate worse, not better. Apple deprecated this API in 2018 specifically because it causes these issues.
The correct fix is to handle the waiting state properly, which Network.framework provides. This will actually solve the cellular failures because the framework will automatically retry when network becomes available (e.g., user walks out of elevator, WiFi returns).
Implementation time: 15 minutes to add waiting state handler vs 2-4 hours debugging reachability race conditions. The waiting state approach is both faster AND more reliable."
*“我理解截止日期的压力。但添加SCNetworkReachability会引入竞态条件,导致15%的失败率变得更糟,而非改善。Apple在2018年废弃该API正是因为它会引发此类问题。
正确的修复方式是正确处理等待状态,这是Network.framework原生支持的功能。这能真正解决蜂窝网络失败问题,因为框架会在网络恢复时自动重试(例如用户走出电梯后WiFi恢复)。
实现时间:添加等待状态处理需15分钟,而调试可达性竞态条件需2-4小时。等待状态方案不仅更快,而且更可靠。”*
Time saved
时间节省
- Reachability approach 30 min to implement + 2-4 hours debugging race conditions + potential App Store rejection = 3-5 hours total
- Waiting state approach 15 minutes to implement + 0 hours debugging = 15 minutes total
- Savings 2.5-4.5 hours + avoiding App Store review issues
- 可达性方案 30分钟实现 + 2-4小时调试竞态条件 + 潜在App Store拒绝 = 总计3-5小时
- 等待状态方案 15分钟实现 + 0小时调试 = 15分钟
- 节省时间 2.5-4.5小时,同时避免App Store审核问题
Actual root cause of 15% cellular failures
15%蜂窝网络失败的实际根因
Likely missing waiting state handler. When user is in area with weak cellular, connection moves to waiting state. Without handler, app shows "Connection failed" instead of "Waiting for network," so user force-quits and reports "doesn't work on cellular."
很可能是缺失等待状态处理。当用户处于蜂窝信号弱的区域时,连接进入等待状态。如果没有处理该状态,应用会显示“连接失败”而非“等待网络连接”,用户会强制退出应用并报告“蜂窝网络下无法使用”。
Scenario 2: Blocking Socket Call Causing Main Thread Hang
场景2:阻塞式Socket调用导致主线程挂起
Context
背景
Your app has 1-star reviews: "App freezes for 5-10 seconds randomly." After investigation, you find a "quick" socket connect() call on the main thread. Your tech lead says: "This is a legacy code path from 2015. It only connects to localhost (127.0.0.1), so it should be instant. The real fix is a 3-week refactor to move all networking to a background queue, but we don't have time. Just leave it for now."
你的应用收到1星评价:“应用随机冻结5-10秒。”调查后发现主线程上有一个“快速”socket connect()调用。你的技术负责人说:“这是2015年的遗留代码路径,仅连接到localhost(127.0.0.1),应该是即时完成的。真正的修复需要3周时间将所有网络代码迁移至后台队列,但我们没有时间。先保持现状。”
Pressure signals
压力信号
- ⏰ Time pressure "3-week refactor, we're in feature freeze for 2.0 launch"
- 💸 Sunk cost "This code has worked for 8 years, why change it now?"
- 🎯 Scope pressure "It's just localhost, not a real network call"
- 📊 Low frequency "Only 2% of users see this freeze"
- ⏰ 时间压力 “3周重构,我们正处于2.0版本的功能冻结期”
- 💸 沉没成本 “这段代码已经稳定运行8年,为什么现在要改?”
- 🎯 范围压力 “只是连接到localhost,不是真实的网络调用”
- 📊 低频率 “仅2%的用户遇到冻结问题”
Rationalization trap
合理化陷阱
"Connecting to localhost is basically instant. The freeze must be caused by something else. Besides, refactoring this legacy code is risky—what if I break something? Better to leave working code alone and focus on the new features for 2.0."
“连接到localhost基本是即时的,冻结肯定是其他原因导致的。此外,重构这段遗留代码有风险——如果我破坏了现有功能怎么办?最好保持现有代码稳定,专注于2.0版本的新功能。”
Why this fails
为何该方案会失败
-
Even localhost can block If the app has many threads, the kernel may schedule other work before returning from connect(). Even 50-100ms is visible to users as a stutter.
-
ANR (Application Not Responding) iOS watchdog will terminate your app if main thread blocks for >5 seconds. This explains "random" crashes.
-
Localhost isn't always available If VPN is active, localhost routing can be delayed. If device is under memory pressure, kernel scheduling is slower.
-
Guaranteed App Store rejection Apple's App Store Review Guidelines explicitly check for main thread blocking. This will fail App Review's performance tests.
-
即使localhost也可能阻塞 如果应用有很多线程,内核可能在返回connect()前调度其他任务。即使50-100ms的延迟,用户也能感知到卡顿。
-
ANR(应用无响应) 如果主线程阻塞超过5秒,iOS看门狗会终止应用,这解释了“随机”崩溃问题。
-
localhost并非始终可用 如果VPN激活,localhost路由可能延迟;如果设备内存不足,内核调度会变慢。
-
必然被App Store拒绝 Apple的App Store审核指南明确检查主线程阻塞情况,这会导致性能测试失败。
MANDATORY response
必须采用的解决方案
swift
// ❌ NEVER call blocking socket APIs on main thread
/*
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, &addr, addrlen) // BLOCKS MAIN THREAD → ANR
*/
// ✅ ALWAYS use async connection, even for localhost
func connectToLocalhost() {
let connection = NWConnection(
host: "127.0.0.1",
port: 8080,
using: .tcp
)
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
print("Connected to localhost")
self?.sendRequest(on: connection)
case .failed(let error):
print("Localhost connection failed: \(error)")
default:
break
}
}
// Non-blocking, returns immediately
connection.start(queue: .main)
}swift
// ❌ 绝对不要在主线程调用阻塞式Socket API
/*
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, &addr, addrlen) // 阻塞主线程 → ANR
*/
// ✅ 即使是localhost,也要始终使用异步连接
func connectToLocalhost() {
let connection = NWConnection(
host: "127.0.0.1",
port: 8080,
using: .tcp
)
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
print("Connected to localhost")
self?.sendRequest(on: connection)
case .failed(let error):
print("Localhost connection failed: \(error)")
default:
break
}
}
// 非阻塞,立即返回
connection.start(queue: .main)
}Alternative: If you must keep legacy socket code (not recommended)
替代方案:如果必须保留遗留Socket代码(不推荐)
swift
// Move blocking call to background queue (minimum viable fix)
DispatchQueue.global(qos: .userInitiated).async {
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, &addr, addrlen) // Still blocks, but not main thread
DispatchQueue.main.async {
// Update UI after connection
}
}swift
// 将阻塞调用移至后台队列(最小可行修复)
DispatchQueue.global(qos: .userInitiated).async {
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, &addr, addrlen) // 仍会阻塞,但不会阻塞主线程
DispatchQueue.main.async {
// 连接后更新UI
}
}Professional push-back template
专业反驳模板
"I understand this code has been stable for 8 years. However, Apple's App Store Review now runs automated performance tests that will fail apps with main thread blocking. This will block our 2.0 release.
The fix doesn't require a 3-week refactor. I can wrap the existing socket code in a background queue dispatch in 30 minutes. Or, I can replace it with NWConnection (non-blocking) in 45 minutes, which also eliminates the socket management code entirely.
Neither approach requires touching other parts of the codebase. We can ship 2.0 on schedule AND fix the ANR crashes."
*“我理解这段代码已经稳定运行8年。但Apple的App Store审核现在会运行自动化性能测试,主线程阻塞的应用会被拒绝,这会阻碍我们2.0版本的发布。
修复不需要3周重构。我可以在30分钟内将现有Socket代码包装在后台队列调度中。或者,我可以用45分钟将其替换为NWConnection(非阻塞),同时消除Socket管理代码。
两种方案都不需要修改代码库的其他部分,我们可以按时发布2.0版本,同时修复ANR崩溃问题。”*
Time saved
时间节省
- Leave it alone 0 hours upfront + 4-8 hours when App Review rejects + user churn from 1-star reviews
- Background queue fix 30 minutes = main thread safe
- NWConnection fix 45 minutes = main thread safe + eliminates socket management
- Savings 3-7 hours + avoiding App Store rejection
- 保持现状 0小时前期投入 + 4-8小时处理App Store拒绝 + 1星评价导致的用户流失
- 后台队列修复 30分钟 → 主线程安全
- NWConnection修复 45分钟 → 主线程安全 + 消除Socket管理
- 节省时间 3-7小时,同时避免App Store拒绝
Scenario 3: Design Review Pressure — "Use WebSockets for Everything"
场景3:设计评审压力——“所有场景都使用WebSocket”
Context
背景
Your team is building a multiplayer game with real-time player positions (20 updates/second). In architecture review, the senior architect says: "All our other apps use WebSockets for networking. We should use WebSockets here too for consistency. It's production-proven, and the backend team already knows how to deploy WebSocket servers."
你的团队正在开发一款实时多人游戏(每秒20次玩家位置更新)。架构评审中,资深架构师说:“我们所有其他应用都使用WebSocket进行网络通信。为了一致性,这里也应该使用WebSocket。它经过生产验证,后端团队也知道如何部署WebSocket服务器。”
Pressure signals
压力信号
- 👔 Authority pressure Senior architect with 15 years experience
- 🏢 Org consistency "All other apps use WebSockets"
- 💼 Backend expertise "Backend team doesn't know UDP"
- 📊 Proven technology "WebSockets are battle-tested"
- 👔 权威压力 拥有15年经验的资深架构师
- 🏢 组织一致性 “所有其他应用都使用WebSocket”
- 💼 后端专业能力 “后端团队不了解UDP”
- 📊 成熟技术 “WebSocket经过实战检验”
Rationalization trap
合理化陷阱
"The architect has way more experience than me. If WebSockets work for the other apps, they'll work here too. UDP sounds complicated and risky. Better to stick with proven technology than introduce something new that might break in production."
“架构师的经验比我丰富得多。如果WebSocket在其他应用中可行,在这里也应该可行。UDP听起来复杂且有风险,最好坚持使用成熟技术,而非引入可能在生产环境中崩溃的新技术。”
Why this fails for real-time gaming
为何该方案不适用于实时游戏
-
Head-of-line blocking WebSockets use TCP. If one packet is lost, TCP blocks ALL subsequent packets until retransmission succeeds. In a game, this means old player position (frame 100) blocks new position (frame 120), causing stutter.
-
Latency overhead TCP requires 3-way handshake (SYN, SYN-ACK, ACK) before sending data. For 20 updates/second, this overhead adds 50-150ms latency.
-
Unnecessary reliability Game position updates don't need guaranteed delivery. If frame 100 is lost, frame 101 (5ms later) makes it obsolete. TCP retransmits frame 100, wasting bandwidth.
-
Connection establishment WebSockets require HTTP upgrade handshake (4 round trips) before data transfer. UDP starts sending immediately.
-
队头阻塞 WebSocket使用TCP。如果一个数据包丢失,TCP会阻塞所有后续数据包直到重传成功。在游戏中,这意味着旧的玩家位置(帧100)会阻塞新位置(帧120),导致卡顿。
-
延迟开销 TCP需要三次握手(SYN、SYN-ACK、ACK)才能发送数据。对于每秒20次更新,该开销会增加50-150ms延迟。
-
不必要的可靠性 游戏位置更新不需要可靠交付。如果帧100丢失,5ms后的帧101会使其过时。TCP重传帧100只是浪费带宽。
-
连接建立 WebSocket需要HTTP升级握手(4次往返)才能传输数据,而UDP可立即发送数据。
MANDATORY response
必须采用的解决方案
swift
// ❌ WRONG for real-time gaming
/*
let webSocket = URLSession.shared.webSocketTask(with: url)
webSocket.resume()
webSocket.send(.data(positionUpdate)) { error in
// TCP guarantees delivery but blocks on loss
// Old position blocks new position → stutter
}
*/
// ✅ CORRECT for real-time gaming
let connection = NWConnection(
host: NWEndpoint.Host("game-server.example.com"),
port: NWEndpoint.Port(integerLiteral: 9000),
using: .udp
)
connection.stateUpdateHandler = { state in
if case .ready = state {
print("Ready to send game updates")
}
}
connection.start(queue: .main)
// Send player position updates (20/second)
func sendPosition(_ position: PlayerPosition) {
let data = encodePosition(position)
connection.send(content: data, completion: .contentProcessed { error in
// Fire and forget, no blocking
// If this frame is lost, next frame (50ms later) makes it obsolete
})
}swift
// ❌ 实时游戏场景下错误
/*
let webSocket = URLSession.shared.webSocketTask(with: url)
webSocket.resume()
webSocket.send(.data(positionUpdate)) { error in
// TCP保证交付,但丢失时会阻塞
// 旧位置阻塞新位置 → 卡顿
}
*/
// ✅ 实时游戏场景下正确
let connection = NWConnection(
host: NWEndpoint.Host("game-server.example.com"),
port: NWEndpoint.Port(integerLiteral: 9000),
using: .udp
)
connection.stateUpdateHandler = { state in
if case .ready = state {
print("Ready to send game updates")
}
}
connection.start(queue: .main)
// 发送玩家位置更新(每秒20次)
func sendPosition(_ position: PlayerPosition) {
let data = encodePosition(position)
connection.send(content: data, completion: .contentProcessed { error in
// 发送后无需等待,无阻塞
// 如果此帧丢失,50ms后的下一帧会使其过时
})
}Technical comparison table
技术对比表
| Aspect | WebSocket (TCP) | UDP |
|---|---|---|
| Latency (typical) | 50-150ms | 10-30ms |
| Head-of-line blocking | Yes (old data blocks new) | No |
| Connection setup | 4 round trips (HTTP upgrade) | 0 round trips |
| Packet loss handling | Blocks until retransmit | Continues with next packet |
| Bandwidth (20 updates/sec) | ~40 KB/s | ~20 KB/s |
| Best for | Chat, API calls | Gaming, streaming |
| 维度 | WebSocket(TCP) | UDP |
|---|---|---|
| 典型延迟 | 50-150ms | 10-30ms |
| 队头阻塞 | 是(旧数据阻塞新数据) | 否 |
| 连接建立 | 4次往返(HTTP升级) | 0次往返 |
| 丢包处理 | 阻塞直到重传成功 | 继续发送下一数据包 |
| 带宽(每秒20次更新) | ~40 KB/s | ~20 KB/s |
| 最佳适用场景 | 聊天、API调用 | 游戏、流媒体 |
Professional push-back template
专业反驳模板
"I appreciate the concern about consistency and proven technology. WebSockets are excellent for our other apps because they're doing chat, notifications, and API calls—use cases where guaranteed delivery matters.
However, real-time gaming has different requirements. Let me explain with a concrete example:
Player moves from position A to B to C (3 updates in 150ms). With WebSockets:
- Frame A sent
- Frame A packet lost
- Frame B sent, but TCP blocks it (waiting for Frame A retransmit)
- Frame C sent, also blocked
- Frame A retransmits, arrives 200ms later
- Frames B and C finally delivered
- Result: 200ms of frozen player position, then sudden jump to C
With UDP:
- Frame A sent and lost
- Frame B sent and delivered (50ms later)
- Frame C sent and delivered (50ms later)
- Result: Smooth position updates, no freeze
The backend team doesn't need to learn UDP from scratch—they can use the same Network.framework on server-side Swift (Vapor, Hummingbird). Implementation time is the same.
I'm happy to do a proof-of-concept this week showing latency comparison. We can measure both approaches with real data."
*“我理解对一致性和成熟技术的考量。WebSocket在我们的其他应用中表现出色,因为它们的场景是聊天、通知和API调用——这些场景需要可靠交付。
但实时游戏有不同的需求。我用一个具体例子解释:
玩家从位置A移动到B再到C(150ms内3次更新)。使用WebSocket:
- 发送帧A
- 帧A数据包丢失
- 发送帧B,但TCP阻塞(等待帧A重传)
- 发送帧C,同样被阻塞
- 帧A重传,200ms后到达
- 帧B和C最终交付
- 结果:玩家位置冻结200ms,然后突然跳至C
使用UDP:
- 发送帧A并丢失
- 发送帧B并交付(50ms后)
- 发送帧C并交付(50ms后)
- 结果:位置更新流畅,无冻结
后端团队无需从头学习UDP——他们可以在服务器端Swift(Vapor、Hummingbird)中使用相同的Network.framework。实现时间相同。
我可以在本周做一个原型演示延迟对比,我们可以用真实数据测试两种方案。”*
When WebSockets ARE correct
WebSocket适用场景
- Chat applications (message delivery must be reliable)
- Turn-based games (moves must arrive in order)
- API calls over persistent connection
- Live notifications/updates
- 聊天应用(消息必须可靠交付)
- 回合制游戏(移动操作必须按顺序到达)
- 基于持久连接的API调用
- 实时通知/更新
Time saved
时间节省
- WebSocket approach 2 days implementation + 1-2 weeks debugging stutter/lag issues + potential rewrite = 3-4 weeks
- UDP approach 2 days implementation + smooth gameplay = 2 days
- Savings 2-3 weeks + better user experience
- WebSocket方案 2天实现 + 1-2周调试卡顿/延迟问题 + 潜在重写 = 3-4周
- UDP方案 2天实现 + 流畅游戏体验 = 2天
- 节省时间 2-3周,同时提升用户体验
Migration Guides
迁移指南
For detailed migration guides from legacy networking APIs, invoke :
/skill axiom-networking-migration- Migration 1: BSD Sockets → NWConnection
- Migration 2: NWConnection → NetworkConnection (iOS 26+)
- Migration 3: URLSession StreamTask → NetworkConnection
如需从遗留网络API迁移的详细指南,请调用:
/skill axiom-networking-migration- 迁移1:BSD Sockets → NWConnection
- 迁移2:NWConnection → NetworkConnection(iOS 26+)
- 迁移3:URLSession StreamTask → NetworkConnection
Checklist
检查清单
Before shipping networking code, verify:
上线前请验证以下内容:
Deprecated API Check
废弃API检查
- Not using SCNetworkReachability anywhere in codebase
- Not using CFSocket, NSSocket, or BSD sockets directly
- Not using NSStream or CFStream
- Not using NSNetService (use NWBrowser instead)
- Not calling getaddrinfo for manual DNS resolution
- 代码库中未使用SCNetworkReachability
- 未直接使用CFSocket、NSSocket或BSD sockets
- 未使用NSStream或CFStream
- 未使用NSNetService(改用NWBrowser)
- 未调用getaddrinfo进行手动DNS解析
Connection Configuration
连接配置
- Using hostname, NOT hardcoded IP address
- TLS enabled for sensitive data (passwords, tokens, user content)
- Handling waiting state with user feedback ("Waiting for network...")
- Not checking reachability before calling connection.start()
- 使用主机名,而非硬编码IP地址
- 敏感数据(密码、令牌、用户内容)已启用TLS
- 已处理等待状态并提供用户反馈(“等待网络连接...”)
- 未在调用connection.start()前检查可达性
Memory Management
内存管理
- Using [weak self] in all NWConnection completion handlers
- Or using NetworkConnection (iOS 26+) with async/await (no [weak self] needed)
- Calling connection.cancel() when done to free resources
- 所有NWConnection完成回调中使用了[weak self]
- 或使用NetworkConnection(iOS 26+)和async/await(无需[weak self])
- 完成后调用connection.cancel()释放资源
Network Transitions
网络切换
- Supporting network changes (WiFi → cellular, or vice versa)
- Using viabilityUpdateHandler or betterPathUpdateHandler (NWConnection)
- Or monitoring connection.states async sequence (NetworkConnection)
- NOT tearing down connection immediately on viability change
- 支持网络变化(WiFi ↔ 蜂窝网络)
- 使用viabilityUpdateHandler或betterPathUpdateHandler(NWConnection)
- 或监听connection.states异步序列(NetworkConnection)
- 网络可用性变化时未立即销毁连接
Testing on Real Devices
真实设备测试
- Tested on real device (not just simulator)
- Tested WiFi → cellular transition (walk out of building)
- Tested Airplane Mode toggle (enable → disable)
- Tested on IPv6-only network (some cellular carriers)
- Tested with corporate VPN active
- Tested with low signal (basement, elevator)
- 在真实设备上测试(而非仅模拟器)
- 测试WiFi切换至蜂窝网络(走出建筑)
- 测试飞行模式切换(启用→禁用)
- 在纯IPv6网络测试(部分蜂窝运营商)
- 测试企业VPN激活状态
- 在弱信号环境测试(地下室、电梯)
Performance
性能
- Using connection.batch for multiple UDP datagrams (30% CPU reduction)
- Using contentProcessed completion for send pacing (not sleep())
- Profiled with Instruments Network template
- Connection establishment < 500ms (check with logging)
- 对多个UDP数据报使用connection.batch(降低30% CPU占用)
- 使用contentProcessed完成回调控制发送节奏(而非sleep())
- 使用Instruments Network模板分析性能
- 连接建立时间<500ms(通过日志检查)
Error Handling
错误处理
- Handling .failed state with specific error
- Timeout handling (don't wait forever in .preparing)
- TLS handshake errors logged for debugging
- User-facing errors are actionable ("Check network" not "POSIX error 61")
- 处理.failed状态并提供具体错误
- 超时处理(不要在.preparing状态无限等待)
- 记录TLS握手错误用于调试
- 用户可见错误具有可操作性(“检查网络”而非“POSIX错误61”)
iOS 26+ Features (if using NetworkConnection)
iOS 26+功能(如果使用NetworkConnection)
- Using TLV framing if need message boundaries
- Using Coder protocol if sending Codable types
- Using NetworkListener instead of NWListener
- Using NetworkBrowser with Wi-Fi Aware for peer-to-peer
- 需要消息边界时使用TLV帧处理
- 发送Codable类型时使用Coder协议
- 使用NetworkListener而非NWListener
- 使用带Wi-Fi Aware的NetworkBrowser进行点对点通信
Real-World Impact
实际业务影响
User-Space Networking: 30% CPU Reduction
用户态网络:降低30% CPU占用
WWDC 2018 Demo Live UDP video streaming comparison:
- BSD sockets ~30% higher CPU usage on receiver
- Network.framework ~30% lower CPU usage
Why Traditional sockets copy data kernel → userspace. Network.framework uses memory-mapped regions (no copy) and reduces context switches from 100 syscalls → ~1 syscall (with batching).
WWDC 2018演示 实时UDP视频流对比:
- BSD sockets 接收端CPU占用高约30%
- Network.framework CPU占用低约30%
原因 传统Socket将数据从内核复制到用户态,Network.framework使用内存映射区域(无复制),并将系统调用从100次减少到约1次(通过批量处理)。
Impact for your app
对应用的影响
- Lower battery drain (30% less CPU = longer battery life)
- Smoother gameplay (more CPU for rendering)
- Cooler device (less thermal throttling)
- 更低的电池消耗(CPU占用降低30% = 更长续航)
- 更流畅的游戏体验(更多CPU资源用于渲染)
- 设备温度更低(减少热节流)
Smart Connection Establishment: 50% Faster
智能连接建立:速度提升50%
Traditional approach
传统方案
- Call getaddrinfo (100-300ms DNS lookup)
- Try first IPv6 address, wait 5 seconds for timeout
- Try IPv4 address, finally connects
- 调用getaddrinfo(100-300ms DNS查询)
- 尝试第一个IPv6地址,等待5秒超时
- 尝试IPv4地址,最终连接
Network.framework (Happy Eyeballs)
Network.framework(Happy Eyeballs)
- Start DNS lookup in background
- As soon as first address arrives, try connecting
- Start second connection attempt 50ms later
- Use whichever connects first
Result 50% faster connection establishment in dual-stack environments (measured by Apple)
- 后台启动DNS查询
- 第一个地址返回后立即尝试连接
- 50ms后启动第二个连接尝试
- 使用先连接成功的地址
结果 双栈环境下连接建立速度提升50%(Apple测试数据)
Proper State Handling: 10x Crash Reduction
正确状态处理:崩溃率降低10倍
Customer report App crash rate dropped from 5% → 0.5% after implementing waiting state handler.
Before App showed "Connection failed" when no network, users force-quit app → crash report.
After App showed "Waiting for network" and automatically retried when WiFi returned → users saw seamless reconnection.
客户报告 实现等待状态处理后,应用崩溃率从5%降至0.5%。
之前 无网络时应用显示“连接失败”,用户强制退出应用 → 崩溃报告。
之后 应用显示“等待网络连接”,WiFi恢复时自动重试 → 用户体验无缝重连。
Resources
参考资源
WWDC: 2018-715, 2025-250
Skills: axiom-networking-diag, axiom-network-framework-ref
Last Updated 2025-12-02
Status Production-ready patterns from WWDC 2018 and WWDC 2025
Tested Patterns validated against Apple documentation and WWDC transcripts
WWDC:2018-715, 2025-250
技能:axiom-networking-diag, axiom-network-framework-ref
最后更新 2025-12-02
状态 基于WWDC 2018和WWDC 2025的生产就绪模式
测试验证 模式已通过Apple文档和WWDC文稿验证