axiom-networking by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-networking在以下场景中使用:
axiom-networking-diag 对连接失败、超时和性能问题进行系统性故障排除axiom-network-framework-ref 获取包含所有 WWDC 示例的完整 API 参考如果你正在做以下任何一项,请停止并使用本技能中的模式:
// ❌ 错误 —— 竞态条件
if SCNetworkReachabilityGetFlags(reachability, &flags) {
connection.start() // 在检查和启动之间网络可能发生变化
}
失败原因 网络状态在可达性检查和 connect() 之间发生变化。你错过了 Network.framework 的智能连接建立(Happy Eyeballs、代理处理、WiFi Assist)。Apple 在 2018 年已弃用此 API。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// ❌ 错误 —— 必然导致 ANR(应用无响应)
let socket = socket(AF_INET, SOCK_STREAM, 0)
connect(socket, &addr, addrlen) // 阻塞主线程
失败原因 主线程挂起 → UI 冻结 → 因响应性问题被 App Store 拒绝。即使是"快速"连接也需要 200-500 毫秒。
// ❌ 错误 —— 错过 Happy Eyeballs、代理、VPN
var hints = addrinfo(...)
getaddrinfo("example.com", "443", &hints, &results)
// 现在手动尝试每个地址...
失败原因 你拙劣地重新实现了 Apple 十多年的连接逻辑。错过了 IPv4/IPv6 竞速、代理评估、VPN 检测。
// ❌ 错误 —— 破坏代理/VPN 兼容性
let host = "192.168.1.1" // 或任何 IP 字面量
失败原因 代理自动配置(PAC)需要主机名来评估规则。VPN 无法正确路由。基于 DNS 的负载均衡失效。
// ❌ 错误 —— 用户体验差
connection.stateUpdateHandler = { state in
if case .ready = state {
// 处理就绪状态
}
// 缺失:.waiting 状态的处理
}
失败原因 用户在飞行模式下看到"连接失败"而不是"等待网络"。当 WiFi 恢复时没有自动重试。
// ❌ 错误 —— 内存泄漏
connection.send(content: data, completion: .contentProcessed { error in
self.handleSend(error) // 循环引用:connection → handler → self → connection
})
失败原因 连接保留完成处理程序,处理程序强捕获 self,self 保留连接 → 内存泄漏。
// ❌ 错误 —— 违反结构化并发
Task {
let connection = NetworkConnection(...)
connection.send(data) // async/await
connection.stateUpdateHandler = { ... } // 完成处理程序 —— 不要混合使用
}
失败原因 NetworkConnection 专为纯 async/await 设计。混合范式会造成困难的错误传播和取消问题。
// ❌ 错误 —— 在 WiFi → 蜂窝网络切换时连接失败
// 没有 viabilityUpdateHandler,没有 betterPathUpdateHandler
// 用户走出大楼 → 连接中断
失败原因 现代应用必须优雅地处理网络变化。40% 的连接失败发生在网络切换期间。
在编写任何网络代码之前,务必完成以下步骤:
// 步骤 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 → 使用连接等待状态
// - CFSocket → 使用 NWConnection
// - NSStream, CFStream → 使用 NWConnection
// - NSNetService → 使用 NWBrowser 或 NetworkBrowser
// - getaddrinfo → 让 Network.framework 处理 DNS
// 搜索命令:
// grep -rn "SCNetworkReachability\|CFSocket\|NSStream\|getaddrinfo" .
使用此决策树在 2 分钟内选择正确的模式:
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
使用场景 iOS 26+ 部署,需要带 TLS 安全性的可靠 TCP,希望使用 async/await
时间成本 10-15 分钟
// 错误 —— 不要这样做
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) // 阻塞!
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
}
}
}
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029),
using: .parameters {
TLS {
TCP {
IP()
.fragmentationEnabled(false)
}
}
}
.constrainedPathsProhibited(true) // 在低数据模式下不使用蜂窝网络
)
-NWLoggingEnabled 1 -NWConnectionLoggingEnabled 1使用场景 iOS 26+ 部署,需要用于游戏或实时流媒体的 UDP 数据报,希望使用 async/await
时间成本 10-15 分钟
// 错误 —— 不要这样做
let sock = socket(AF_INET, SOCK_DGRAM, 0)
let sent = sendto(sock, buffer, length, 0, &addr, addrlen)
// 阻塞,无批处理,CPU 开销高
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)
}
}
使用场景 需要在流协议(TCP/TLS)上保留消息边界,有混合消息类型,希望进行类型安全的消息处理
时间成本 15-20 分钟
背景 流协议(TCP/TLS)不保留消息边界。如果你发送 3 个数据块,接收方可能一次收到 1 个字节,或者一次性全部收到。TLV(类型-长度-值)通过为每条消息编码其类型和长度来解决此问题。
// 错误 —— 容易出错,样板代码多
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
// 现在手动解码...
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)")
}
}
使用场景 发送/接收 Codable 类型,希望消除 JSON 样板代码,需要类型安全的消息处理
时间成本 10-15 分钟
背景 大多数应用手动将 Codable 类型编码为 JSON,发送字节,接收字节,解码 JSON。Coder 协议通过自动处理序列化来消除这些样板代码。
// 错误 —— 样板代码多,容易出错
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)
import Network
// 将消息类型定义为 Codable 枚举
enum GameMessage: Codable {
case selectedCharacter(String)
case move(row: Int, column: Int)
}
// 带 Coder 协议的连接
let connection = NetworkConnection(
to: .hostPort(host: "www.example.com", port: 1029)
) {
Coder(GameMessage.self, using: .json) {
TLS()
}
}
// 直接发送 Codable 类型
public func sendWithCoder() async throws {
let selectedCharacter: GameMessage = .selectedCharacter("🐨")
try await connection.send(selectedCharacter) // 无需编码!
}
// 直接接收 Codable 类型
public func receiveWithCoder() async throws {
let gameMessage = try await connection.receive().content // 返回 GameMessage!
switch gameMessage {
case .selectedCharacter(let character):
print("Character selected: \(character)")
case .move(let row, let column):
print("Move: (\(row), \(column))")
}
}
.json —— JSON 编码(最常见,人类可读).propertyList —— 属性列表编码(更小,更快)对于支持 iOS 12-25 且尚不能使用 async/await 的应用,请调用 /skill axiom-networking-legacy:
距离 App Store 提交还有 3 天。QA 报告在蜂窝网络上连接失败(15% 失败率)。你的产品经理审查代码后建议:"在连接前添加一个可达性检查。如果没有网络,立即显示错误而不是超时。"
"SCNetworkReachability 是 Apple 的 API,它肯定是正确的。我在 Stack Overflow 上看到过有 500+ 赞的回答。添加一个快速的可达性检查今天就能解决问题,我可以在发布后重构它。截止日期比现在写出完美代码更重要。"
竞态条件 网络状态在可达性检查和连接启动之间发生变化。你在 10:00:00.000 检查"WiFi 可用",但 WiFi 在 10:00:00.050 断开,然后你在 10:00:00.100 调用 connection.start()。连接失败,但可达性检查说网络可用。
错过智能连接建立 Network.framework 尝试多种策略(IPv4、IPv6、代理、WiFi Assist 回退到蜂窝网络)。SCNetworkReachability 给你"是/否",但不告诉你哪种策略会奏效。
已弃用的 API Apple 在 WWDC 2018 中明确弃用了 SCNetworkReachability。App Store 审核可能会将其标记为使用旧版 API。
没有解决实际问题 15% 的蜂窝网络失败很可能是因为没有正确处理等待状态,而不是因为缺少可达性检查。
// ❌ 绝对不要在连接前检查可达性
/*
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("Connection failed. Please check your network.")
case .cancelled:
self?.hideStatus()
@unknown default:
break
}
}
connection.start(queue: .main)
"我理解截止日期的压力。然而,添加 SCNetworkReachability 会产生竞态条件,这会使 15% 的失败率变得更糟,而不是更好。Apple 在 2018 年弃用此 API 正是因为会导致这些问题。
正确的修复方法是正确处理等待状态,这是 Network.framework 提供的功能。这将真正解决蜂窝网络失败问题,因为当网络可用时(例如,用户走出电梯,WiFi 恢复),框架会自动重试。
实现时间:添加等待状态处理程序需要 15 分钟,而调试可达性竞态条件需要 2-4 小时。等待状态方法既更快又更可靠。"
很可能是因为缺少等待状态处理程序。当用户处于蜂窝信号弱的区域时,连接会进入等待状态。没有处理程序,应用会显示"连接失败"而不是"等待网络",因此用户强制退出并报告"在蜂窝网络上无法使用"。
你的应用收到一星评价:"应用随机冻结 5-10 秒。" 调查后,你发现主线程上有一个"快速"的 socket connect() 调用。你的技术负责人说:"这是 2015 年的遗留代码路径。它只连接到 localhost(127.0.0.1),所以应该是即时的。真正的修复方法是用 3 周时间重构,将所有网络操作移到后台队列,但我们没有时间。暂时先放着吧。"
"连接到 localhost 基本上是即时的。冻结肯定是由其他原因引起的。此外,重构这段遗留代码有风险——如果我破坏了什么怎么办?最好让能工作的代码保持原样,专注于 2.0 的新功能。"
即使是 localhost 也可能阻塞 如果应用有很多线程,内核可能会在从 connect() 返回之前调度其他工作。即使是 50-100 毫秒,用户也能察觉到卡顿。
ANR(应用无响应) 如果主线程阻塞超过 5 秒,iOS 看门狗将终止你的应用。这解释了"随机"崩溃。
localhost 并不总是可用 如果 VPN 处于活动状态,localhost 路由可能会延迟。如果设备内存压力大,内核调度会更慢。
必然导致 App Store 拒绝 Apple 的 App Store 审核指南明确检查主线程阻塞。这将无法通过 App Review 的性能测试。
// ❌ 绝对不要在主线程上调用阻塞式套接字 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)
}
// 将阻塞调用移到后台队列(最小可行修复)
DispatchQueue.global(qos: .userInitiated).async {
let sock = socket(AF_INET, SOCK_STREAM, 0)
connect(sock, &addr, addrlen) // 仍然阻塞,但不是主线程
DispatchQueue.main.async {
// 连接后更新 UI
}
}
"我理解这段代码已经稳定运行了 8 年。然而,Apple 的 App Store 审核现在运行自动化的性能测试,会拒绝有主线程阻塞的应用。这将阻碍我们的 2.0 版本发布。
修复不需要 3 周的重构。我可以在 30 分钟内用后台队列调度包装现有的套接字代码。或者,我可以在 45 分钟内用 NWConnection(非阻塞)替换它,这还能消除套接字管理代码。
两种方法都不需要触及代码库的其他部分。我们既可以按计划发布 2.0 版本,又可以修复 ANR 崩溃。"
你的团队正在构建一个具有实时玩家位置(每秒 20 次更新)的多人游戏。在架构评审中,高级架构师说:"我们所有其他应用都使用 WebSockets 进行网络通信。为了保持一致性,这里也应该使用 WebSockets。它经过生产验证,后端团队已经知道如何部署 WebSocket 服务器。"
"架构师的经验比我丰富得多。如果 WebSockets 对其他应用有效,那么在这里也应该有效。UDP 听起来复杂且有风险。最好坚持使用经过验证的技术,而不是引入可能在生产中出问题的新东西。"
队头阻塞 WebSockets 使用 TCP。如果一个数据包丢失,TCP 会阻塞所有后续数据包,直到重传成功。在游戏中,这意味着旧的玩家位置(第 100 帧)会阻塞新的位置(第 120 帧),导致卡顿。
延迟开销 TCP 在发送数据前需要三次握手(SYN、SYN-ACK、ACK)。对于每秒 20 次更新,这种开销会增加 50-150 毫秒的延迟。
不必要的可靠性 游戏位置更新不需要保证交付。如果第 100 帧丢失,第 101 帧(5 毫秒后)使其过时。TCP 重传第 100 帧,浪费带宽。
连接建立 WebSockets 在数据传输前需要 HTTP 升级握手(4 次往返)。UDP 立即开始发送。
// ❌ 对实时游戏是错误的
/*
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
// 发送后不管,不阻塞
// 如果此帧丢失,下一帧(50 毫秒后)使其过时
})
}
| 方面 | WebSocket (TCP) | UDP |
|---|---|---|
| 延迟(典型) | 50-150 毫秒 | 10-30 毫秒 |
| 队头阻塞 | 是(旧数据阻塞新数据) | 否 |
| 连接建立 | 4 次往返(HTTP 升级) | 0 次往返 |
| 数据包丢失处理 | 阻塞直到重传 | 继续发送下一个数据包 |
| 带宽(每秒 20 次更新) | 约 40 KB/秒 | 约 20 KB/秒 |
| 最适合 | 聊天、API 调用 | 游戏、流媒体 |
"我理解对一致性和成熟技术的担忧。WebSockets 对我们的其他应用来说非常出色,因为它们用于聊天、通知和 API 调用——这些是需要保证交付的场景。
然而,实时游戏有不同的要求。让我用一个具体例子来解释:
玩家从位置 A 移动到 B 再到 C(150 毫秒内 3 次更新)。使用 WebSockets: - 发送 A 帧 - A 帧数据包丢失 - 发送 B 帧,但 TCP 阻塞它(等待 A 帧重传) - 发送 C 帧,同样被阻塞 - A 帧重传,200 毫秒后到达 - B 帧和 C 帧最终送达 - 结果:玩家位置冻结 200 毫秒,然后突然跳到 C
使用 UDP: - 发送 A 帧并丢失 - 发送 B 帧并送达(50 毫秒后) - 发送 C 帧并送达(50 毫秒后) - 结果:平滑的位置更新,无冻结
后端团队不需要从头学习 UDP——他们可以在服务器端 Swift(Vapor、Hummingbird)上使用相同的 Network.framework。实现时间是一样的。
我很乐意在本周做一个概念验证,展示延迟对比。我们可以用真实数据测量两种方法。"
Use when:
axiom-networking-diag for systematic troubleshooting of connection failures, timeouts, and performance issuesaxiom-network-framework-ref for comprehensive API reference with all WWDC examplesIf you're doing ANY of these, STOP and use the patterns in this skill:
// ❌ 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.
// ❌ WRONG — Guaranteed ANR (Application Not Responding)
let socket = socket(AF_INET, SOCK_STREAM, 0)
connect(socket, &addr, addrlen) // Blocks main thread
Why this fails Main thread hang → frozen UI → App Store rejection for responsiveness. Even "quick" connects take 200-500ms.
// ❌ 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.
// ❌ WRONG — Breaks proxy/VPN compatibility
let host = "192.168.1.1" // or any IP literal
Why this fails Proxy auto-configuration (PAC) needs hostname to evaluate rules. VPNs can't route properly. DNS-based load balancing broken.
// ❌ 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.
// ❌ 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.
// ❌ 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.
// ❌ WRONG — Connection fails on WiFi → cellular transition
// No viabilityUpdateHandler, no betterPathUpdateHandler
// User walks out of building → connection dies
Why this fails Modern apps must handle network changes gracefully. 40% of connection failures happen during network transitions.
ALWAYS complete these steps before writing any networking code:
// 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" .
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
Use when iOS 26+ deployment, need reliable TCP with TLS security, want async/await
Time cost 10-15 minutes
// 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!
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
}
}
}
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
)
-NWLoggingEnabled 1 -NWConnectionLoggingEnabled 1Use when iOS 26+ deployment, need UDP datagrams for gaming or real-time streaming, want async/await
Time cost 10-15 minutes
// 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 overhead
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)
}
}
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.
// 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...
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)")
}
}
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.
// 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)
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))")
}
}
.json — JSON encoding (most common, human-readable).propertyList — Property list encoding (smaller, faster)For apps supporting iOS 12-25 that can't use async/await yet, invoke /skill axiom-networking-legacy:
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."
"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."
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.
// ❌ 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)
"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."
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."
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."
"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."
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.
// ❌ 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)
}
// 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
}
}
"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."
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."
"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."
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.
// ❌ 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
})
}
| 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 |
"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."
For detailed migration guides from legacy networking APIs, invoke /skill axiom-networking-migration:
Before shipping networking code, verify:
WWDC 2018 Demo Live UDP video streaming comparison:
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).
Result 50% faster connection establishment in dual-stack environments (measured by Apple)
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.
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
Weekly Installs
94
Repository
GitHub Stars
601
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode79
codex74
claude-code73
gemini-cli72
cursor70
github-copilot69
GitHub Actions CI/CD 流水线模式:自动化构建、测试与部署全指南
129 周安装
Vercel部署与构建指南:自动化构建流水线、预览部署、发布控制与即时回滚
129 周安装
阿里云CDN OpenAPI自动化操作指南 - 域名管理、缓存刷新、HTTPS证书配置
129 周安装
pnpm 完全指南:JavaScript/TypeScript 包管理器安装、配置与最佳实践
134 周安装
FAISS 向量相似性搜索库 - Meta AI 十亿级向量快速检索,支持 GPU 加速与 Python 集成
130 周安装
Figma设计插件开发与组件系统指南:从自动布局到设计系统管理
130 周安装