axiom-networking-legacy について
iOS 12〜25対応アプリ向けのSwiftネットワークリファレンススキルです。コールバック方式のNWConnectionを使ったTLS、UDPの一括送信、接続待ち受け、Bonjourによる検出を扱います。
主な機能
- 01NWConnectionによるTLS接続
- 02UDPデータグラムの一括送信
- 03NWListenerによる接続受け入れ
- 04Bonjourサービスの検出
活用シーン
- 01iOS 12〜25向けアプリの保守
- 02TLSによるネットワーク接続の暗号化
- 03頻繁なUDP送信の負荷軽減
- 04ローカルネットワーク上のピア検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-networking-legacyインストール後、Claude Code で/axiom-networking-legacyを入力して読み込まれたか確認します。
詳細
レガシー iOS 12-25 の NWConnection パターン
これらのパターンでは、iOS 12-25 をサポートするアプリで、完了ハンドラーを使って NWConnection を使用します。アプリのターゲットが iOS 26 以降の場合は、代わりに async/await を使う NetworkConnection を使用してください(axiom-network-framework-ref スキルを参照)。
パターン 2a:TLS を使った NWConnection(iOS 12-25)
使用する場面 iOS 12-25 をサポートしており、TLS 暗号化が必要で、まだ async/await を使用できない場合
所要時間 10-15 分
GOOD:完了ハンドラーを使った NWConnection
import Network
// Create connection with TLS
let connection = NWConnection(
host: NWEndpoint.Host("mail.example.com"),
port: NWEndpoint.Port(integerLiteral: 993),
using: .tls // TCP inferred
)
// Handle connection state changes
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
print("Connection established")
self?.sendInitialData()
case .waiting(let error):
print("Waiting for network: \(error)")
// Show "Waiting..." UI, don't fail immediately
case .failed(let error):
print("Connection failed: \(error)")
case .cancelled:
print("Connection cancelled")
default:
break
}
}
// Start connection
connection.start(queue: .main)
// Send data with pacing
func sendData() {
let data = Data("Hello, world!".utf8)
connection.send(content: data, completion: .contentProcessed { [weak self] error in
if let error = error {
print("Send error: \(error)")
return
}
// contentProcessed callback = network stack consumed data
// This is when you should send next chunk (pacing)
self?.sendNextChunk()
})
}
// Receive exact byte count
func receiveData() {
connection.receive(minimumIncompleteLength: 10, maximumLength: 10) { [weak self] (data, context, isComplete, error) in
if let error = error {
print("Receive error: \(error)")
return
}
if let data = data {
print("Received \(data.count) bytes")
// Process data...
self?.receiveData() // Continue receiving
}
}
}
NetworkConnection との主な違い
- 保持サイクルを防ぐため、すべての完了ハンドラーで
[weak self]を使用する必要があります - stateUpdateHandler は非同期シーケンスではなく、状態を受け取ります
- send/receive は async/await ではなく、完了コールバックを使用します
使用する場面
- iOS 12-15 をサポートする場合(2024 年時点でデバイスの 70%)
- まだ async/await を使用していないコードベース
- 下位互換性が必要なライブラリ
NetworkConnection への移行(iOS 26+)
- stateUpdateHandler -> connection.states 非同期シーケンス
- 完了ハンドラー -> try await 呼び出し
- [weak self] -> 不要になります(async/await がキャンセルを処理します)
パターン 2b:NWConnection による UDP バッチ処理(iOS 12-25)
使用する場面 iOS 12-25 をサポートし、複数の UDP データグラムを効率的に送信して、CPU 使用率を約 30%削減する必要がある場合
所要時間 10-15 分
背景 従来の UDP ソケットでは、システムコールごとに 1 つのデータグラムを送信します。小さなパケットを 100 個送信すると、コンテキストスイッチが 100 回発生します。バッチ処理を行うと、これを約 1 回のシステムコールに削減できます。
BAD:UDP を個別に送信(CPU 使用率が高い)
// WRONG — 100 context switches for 100 packets
for frame in videoFrames {
sendto(socket, frame.bytes, frame.count, 0, &addr, addrlen)
// Each send = context switch to kernel
}
GOOD:UDP をバッチ送信(CPU 使用率を 30%削減)
import Network
// UDP connection
let connection = NWConnection(
host: NWEndpoint.Host("stream-server.example.com"),
port: NWEndpoint.Port(integerLiteral: 9000),
using: .udp
)
connection.stateUpdateHandler = { state in
if case .ready = state {
print("Ready to send UDP")
}
}
connection.start(queue: .main)
// Batch sending for efficiency
func sendVideoFrames(_ frames: [Data]) {
connection.batch {
for frame in frames {
connection.send(content: frame, completion: .contentProcessed { error in
if let error = error {
print("Send error: \(error)")
}
})
}
}
// All sends batched into ~1 syscall
// 30% lower CPU usage vs individual sends
}
// Receive UDP datagrams
func receiveFrames() {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] (data, context, isComplete, error) in
if let error = error {
print("Receive error: \(error)")
return
}
if let data = data {
// Process video frame
self?.displayFrame(data)
self?.receiveFrames() // Continue receiving
}
}
}
パフォーマンス特性
- バッチ処理なし 100 個のデータグラム = 100 回のシステムコール = 100 回のコンテキストスイッチ
- バッチ処理あり 100 個のデータグラム = 約 1 回のシステムコール = 1 回のコンテキストスイッチ
- 結果 CPU 使用率を約 30%削減(Instruments で測定)
使用する場面
- リアルタイムの動画/音声ストリーミング
- 頻繁に更新されるゲーム(プレイヤーの位置など)
- 高頻度のセンサーデータ(IoT)
WWDC 2018 のデモ ユーザー空間ネットワークとバッチ処理を使ったライブ動画ストリーミングでは、受信側の CPU 使用率が 30%低下しました
パターン 2c:NWListener(iOS 12-25)
使用する場面 着信接続を受け入れる必要があり、サーバーやピアツーピアアプリを構築していて、iOS 12-25 をサポートする場合
所要時間 20-25 分
BAD:ソケットの手動リスニング
// WRONG — Manual socket management
let sock = socket(AF_INET, SOCK_STREAM, 0)
bind(sock, &addr, addrlen)
listen(sock, 5)
while true {
let client = accept(sock, nil, nil) // Blocks thread
// Handle client...
}
GOOD:接続を自動処理する NWListener
import Network
// Create listener with default parameters
let listener = try NWListener(using: .tcp, on: 1029)
// Advertise Bonjour service
listener.service = NWListener.Service(name: "MyApp", type: "_myservice._tcp")
// Handle service registration updates
listener.serviceRegistrationUpdateHandler = { update in
switch update {
case .add(let endpoint):
if case .service(let name, let type, let domain, _) = endpoint {
print("Advertising as: \(name).\(type)\(domain)")
}
default:
break
}
}
// Handle incoming connections
listener.newConnectionHandler = { [weak self] newConnection in
print("New connection from: \(newConnection.endpoint)")
// Configure connection
newConnection.stateUpdateHandler = { state in
switch state {
case .ready:
print("Client connected")
self?.handleClient(newConnection)
case .failed(let error):
print("Client connection failed: \(error)")
default:
break
}
}
// Start handling this connection
newConnection.start(queue: .main)
}
// Handle listener state
listener.stateUpdateHandler = { state in
switch state {
case .ready:
print("Listener ready on port \(listener.port ?? 0)")
case .failed(let error):
print("Listener failed: \(error)")
default:
break
}
}
// Start listening
listener.start(queue: .main)
// Handle client data
func handleClient(_ connection: NWConnection) {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] (data, context, isComplete, error) in
if let error = error {
print("Receive error: \(error)")
return
}
if let data = data {
print("Received \(data.count) bytes")
// Echo back
connection.send(content: data, completion: .contentProcessed { error in
if let error = error {
print("Send error: \(error)")
}
})
self?.handleClient(connection) // Continue receiving
}
}
}
使用する場面
- ピアツーピアアプリ(ファイル共有、メッセージング)
- ローカルネットワークサービス
- 開発/テスト用サーバー
Bonjour のアドバタイズ
- ローカルネットワーク上のサービスを自動検出します
- IP アドレスをハードコードする必要はありません
- 検出には NWBrowser を使用できます
セキュリティに関する考慮事項
- 暗号化には TLS パラメーターを使用します:
NWListener(using: .tls, on: port) - データを処理する前に、クライアント接続を検証します
- DoS を防ぐため、接続数の上限を設定します
パターン 2d:ネットワーク検出(iOS 12-25)
使用する場面 ローカルネットワーク上のサービス(Bonjour)を検出する必要があり、ピアツーピアアプリを構築していて、iOS 12-25 をサポートする場合
所要時間 25-30 分
BAD:ハードコードされた IP アドレス
// WRONG — Brittle, requires manual configuration
let connection = NWConnection(host: "192.168.1.100", port: 9000, using: .tcp)
// What if IP changes? What if multiple devices?
GOOD:サービス検出に NWBrowser を使用
import Network
// Browse for services on local network
let browser = NWBrowser(for: .bonjour(type: "_myservice._tcp", domain: nil), using: .tcp)
// Handle discovered services
browser.browseResultsChangedHandler = { results, changes in
for result in results {
switch result.endpoint {
case .service(let name, let type, let domain, _):
print("Found service: \(name).\(type)\(domain)")
// Connect to this service
self.connectToService(result.endpoint)
default:
break
}
}
}
// Handle browser state
browser.stateUpdateHandler = { state in
switch state {
case .ready:
print("Browser ready")
case .failed(let error):
print("Browser failed: \(error)")
default:
break
}
}
// Start browsing
browser.start(queue: .main)
// Connect to discovered service
func connectToService(_ endpoint: NWEndpoint) {
let connection = NWConnection(to: endpoint, using: .tcp)
connection.stateUpdateHandler = { state in
if case .ready = state {
print("Connected to service")
}
}
connection.start(queue: .main)
}
使用する場面
- ピアツーピア検出(AirDrop のような機能)
- ローカルネットワーク上のプリンター、メディアサーバー
- 開発/テスト(テストサーバーを自動検出)
パフォーマンス特性
- mDNS ベース(マルチキャスト DNS。中央サーバーは不要)
- 同じサブネット上ではほぼ瞬時に検出
- サービスの出現/消失に応じて自動更新
iOS 26 以降の代替手段
- インフラなしのピアツーピア通信には、Wi-Fi Aware を使う NetworkBrowser を使用します
- axiom-network-framework-ref スキルのパターン 1d を参照してください
リソース
スキル:axiom-networking、axiom-network-framework-ref、axiom-networking-migration