For iOS developers · debug builds only
SimMonkey docks beside the iOS Simulator and puts each request your app makes under your control. Stub it, fail it, slow it down, send it somewhere else — and the next request the app makes gets exactly that. No rebuild. No relaunch. Nothing to install in the simulator.
$ [simmonkey] 1.0.0 attached — panel at 127.0.0.1:8377
The problem
The backend is up, so you never see the offline state. It's fast, so the skeleton never shows. It returns well-formed data, so the parser never meets the field that's missing.
You can mock it, and rebuild for every scenario you thought of in advance. You can proxy it, after installing a certificate in the simulator and doing it again after every reset. Or you can ask the backend team for a broken endpoint on staging, and wait.
How it works
Three steps, in this order. The order is the only part that matters.
#if DEBUG
SimMonkey.start()
#endif
In your App initialiser, before anything creates a
URLSession. Keep the guard: the package has no business in a
release build,
and that's what keeps it out.
It finds the simulator window, docks beside it, and follows when you drag. From the moment your app launches, every request it makes appears in the panel as it happens — method, path, status, timing.
Pick a status, paste a body, add a delay, or hand back a real NSURLError.
Save. The next matching request gets it. The app never restarts, and you never touched the backend.
What it does
Traffic
Headers, cookies, both bodies — including the Cookie header
URLSession adds below the level your code can see. Click a row and the panel splits:
request on the left, response on the right.
Rules
Match on the URL — or on the request body too, for the service that answers every operation from one endpoint and only a field in the JSON tells them apart. Paste the fields that matter:
{ "gridName": "ForeignPaymentOrderCodebookFlat" }
Order, whitespace and every other field are ignored. First matching rule wins; drag to reorder.
Failure & latency
A rule can hand your app a genuine NSURLErrorNotConnectedToInternet,
not a mocked-out success — so it's your real error path on screen. A three-second delay holds the
loading state long enough to actually look at it. Cancel mid-delay and nothing arrives afterwards.
Inspection
JSON renders as a collapsible tree that opens the parts worth opening. A 434 KB
station list arrives as 401 rows, not fifteen thousand. Bodies that aren't
what their Content-Type claims still show — decoded, and labelled
so you know your server is lying.
Forward
Send matching requests on to a real server with the URL rewritten — a whole path moved to staging
with $1 — and a test token injected. Status and body come
back from the server, not from you.
Edit & Send
Take any captured request, edit the body, press ⌘↩. The response lands beside it. Cookies go out exactly as captured, so an authenticated replay stays in the session.
The guarantee
Every failure inside the bridge — panel not running, port refused, panel gone mid-session — resolves to the same thing: pass the request through untouched, in a few milliseconds. A stopped tool never looks like a broken app.
Nothing to install in the simulator, nothing in your entitlements, no permission prompt on the Mac. Forwarded requests share the app's cookie jar, so a session established at login keeps working with the panel attached.
What it won't do
URLSession.shared, custom sessions, Alamofire,
AsyncImage — even progressive AVPlayer
streams, which pass through and play.
WKWebView content — separate process.Network.framework or raw sockets.#if DEBUG.Get it
The panel
SimMonkey.app · macOS 14+ · Apple Silicon
DownloadUnzip. Double-click. It finds the simulator.
The package
SimMonkeyKit · iOS 15+ · Swift Package
SimMonkeyKit on GitHubAdd as a package. Never copy the source — copies drift.