
Testing a networking layer on office Wi-Fi tells you almost nothing. Install Apple's Network Link Conditioner (NLC) from the Additional Tools for Xcode package on a hosted Mac, enable the Very Bad Network preset, and confirm your URLSession retry and timeout logic triggers against the iOS Simulator. For Xcode plus Simulator plus a remote-desktop session running together, a Figment M-series Mac starts at $95/month; physical i3 ($49/month) and M-series plans are request-based until supply is confirmed. The $29/month virtual Intel macOS tier runs compatible Intel Xcode (Xcode 26 or earlier per Apple's porting guide) and can host the iOS Simulator and NLC pane, but it has no GPU acceleration or audio and is accessed through a browser console, so throttling behavior there is best confirmed by running the Simulator on an M-series Mac or by agreeing the VM configuration in the quote first.
NLC is a macOS preference pane that throttles the host network stack with configurable bandwidth, latency and packet-loss settings. Because the iOS Simulator uses the host Mac's network, every NLC profile you apply affects the Simulator too — no device cables, no provisioning profiles.
NLC does not ship with Xcode by default. It lives in the Additional Tools for Xcode package on Apple's developer downloads site:
.dmg and open the Hardware folder. Inside is Network Link Conditioner.prefPane.The download page works the same from the browser console on a hosted Mac — you just stay signed in to the Apple developer portal in Safari.
Connect using the access path agreed in your Figment quote (browser console on the $29 VM; agreed remote-desktop path on physical plans). Then:
If the pane doesn't appear, remove any stale copy from ~/Library/PreferencePanes/ and reinstall. On fresh macOS versions Apple occasionally gates third-party panes behind Privacy & Security; approve it once and re-open System Settings.
NLC ships with five built-in presets. Read the exact numbers from the pane on your installed macOS — Apple has adjusted the defaults between releases — and use this table as the real-world mapping, not a precise spec sheet:
| Preset | Approx. down / up BW | Loss | Approx. delay | Real-world scenario |
|---|---|---|---|---|
| Edge | 240 / 200 Kbps | 0% | 400 / 440 ms | Rural 2G fallback |
| 3G | 780 / 330 Kbps | 0% | ~100 ms | Indoor 3G cell signal |
| LTE | ~50 / 50 Mbps | 0% | ~65 / 50 ms | Clean LTE outdoors |
| Very Bad Network | ~1 / 1 Mbps | ~10% | ~500 ms | Congested café Wi-Fi |
| 100% Loss | — | 100% | — | Airplane mode / dead zone |
Turn a preset ON in the pane; it takes effect on every connection the Mac makes, including your remote-desktop session. If VNC-style sessions start lagging, switch to LTE or disable NLC before navigating the desktop, then re-enable the target preset just before the test run.
Click Manage Profiles… (or +, depending on macOS version) to add a scenario your production telemetry actually shows:
A realistic "roaming abroad" profile: 1 Mbps down, 512 Kbps up, 5% loss, 350 ms each way, 200 ms DNS delay.
Run your app in the iOS Simulator with a preset enabled and exercise one real network path. The example below is an unexecuted sketch — copy it into your own test target and point it at an endpoint you control, then run twice (NLC OFF, NLC Very Bad Network) and compare actual elapsed times and error paths:
import XCTest
final class NetworkConditionTest: XCTestCase {
func testTimeoutAndRetryFire() async throws {
// Use an endpoint you own; httpbin is illustrative only.
let url = URL(string: "https://example.invalid/api/ping")!
var request = URLRequest(url: url, timeoutInterval: 3.0)
request.httpMethod = "GET"
let start = Date()
do {
let (_, response) = try await URLSession.shared.data(for: request)
let code = (response as? HTTPURLResponse)?.statusCode ?? -1
print("ok status=\(code) elapsed=\(Date().timeIntervalSince(start))")
} catch {
// A timeout or NSURLErrorNetworkConnectionLost is the expected
// branch under Very Bad Network. Assert your retry layer was
// invoked (e.g. via an injected mock), not just that time passed.
print("error=\(error) elapsed=\(Date().timeIntervalSince(start))")
throw error
}
}
}
Two rules for this kind of test:
NLC stays on across reboots. Leave Edge or 100% Loss enabled and your next xcodebuild, brew or CI agent heartbeat will crawl.
Use only a documented control method for the installed conditioner. networksetup and scutil do not by themselves select Network Link Conditioner profiles. Restore the prior state after the test and verify connectivity. A shell trap is useful for normal exits but cannot recover from every process or machine failure.
Request a Mac configuration or get started on /mac-hosting/start.
Related guides: