Following up on TN3135 and the resolution in
https://developer.apple.com/forums/thread/773362 — that thread solved establishing
a low-level connection on watchOS (the asynchronous
AVAudioSession.activate(options:completionHandler:) instead of the synchronous
setActive()). This question is about a connection staying established, which I could
not find discussed anywhere.
Environment: Apple Watch, watchOS 26.6 (23U67). Audio app, WKBackgroundModes =
["self-care"]. Real device, TestFlight build, not the simulator.
What works
Opening an NWConnection WebSocket to my own server is reliable — 8 attempts out of 8
reached .ready in 0.28–0.98 s, and an echo frame round-tripped in 27–89 ms.
Interestingly, in my measurements it opens under BOTH activation variants: the
asynchronous activate(options:completionHandler:) AND the synchronous setActive(true).
The two are within ~0.2 s of each other. I mention it only because the thread above
concluded the synchronous one is insufficient; on 26.6 I cannot reproduce that
difference for establishment.
What fails
The connection goes quiet after roughly half a minute, and an NWPathMonitor running
alongside it shows why: the path transitions to .unsatisfied. Four runs:
+34.0 s (cellular)
+34.6 s (cellular)
+36.0 s (Wi-Fi)
+34.3 s (companion link only — availableInterfaces ["other", "other"])
The server sends a heartbeat frame every 5 s and closes the socket on a schedule, so I
can tell "the peer closed" from "we stopped receiving". The client receives beats 1–6
(5 s … 30 s) and then nothing; the scheduled close never arrives.
What I ruled out
Server side. The same client construction run on macOS against the same endpoint
receives all 8 heartbeats and the scheduled close at 45.1 s.
Both audio-session activation variants — no difference, as above.
Network type — cellular, Wi-Fi and companion-link-only all drop at ~35 s.
The app being suspended. The app keeps logging densely throughout, and in the last
run it held a WKExtendedRuntimeSession (delegate reported extendedRuntimeSessionDidStart)
and was actively playing audio through AVAudioEngine from the first second — i.e. the
audio-streaming condition TN3135 describes — for the entire window. The path dropped
anyway, at +34.3 s.
An idle socket. Server traffic arrives every 5 s until the drop.
The comparison that puzzles me
The same app, on the same watch, the same afternoon, relays the same realtime audio
session over plain HTTPS (URLSession) instead — and that runs for 64 s continuously
without a stall, including straight through a WatchConnectivity "reachability settled:
unreachable" transition.
So a high-level URLSession request stream survives a period in which a low-level
NWConnection's path is reported unsatisfied. That is consistent with the note in thread
773362 that "on watchOS every session is kinda like a background session, where the
actual work is done out of process" — but it leaves me unsure what the intended
behaviour is.
Questions
Is a ~35 s path lifetime the expected behaviour for low-level networking on watchOS,
or does it indicate something wrong on my side?
Does the TN3135 audio-streaming exception cover only the establishment of a
low-level connection, or is it also supposed to keep the path available for the
duration of the audio streaming?
If it is supposed to persist: is there something beyond an active audio session,
flowing audio and a WKExtendedRuntimeSession that an app must do to keep the path
alive?
If ~35 s is the expected ceiling, is a WebSocket a supported transport for a
multi-minute conversational audio session on watchOS at all — or is relaying over
URLSession the intended approach despite the guidance to prefer Network framework?
Happy to file a bug with a sysdiagnose and a reduced sample project if that is more
useful — please say the word and I will attach the numbers above.
4
0
1.2k