Mobile Development 20 min read

How to Build a Robust iOS Network Debugger Without Re‑sending Requests

This article explains the challenges of iOS network request debugging, reviews existing external and in‑app tools, dives into the URL loading system and URLProtocol mechanics, and presents a delegate‑proxy solution that captures all traffic without altering the original requests, even in production environments.

Jike Tech Team
Jike Tech Team
Jike Tech Team
How to Build a Robust iOS Network Debugger Without Re‑sending Requests

Current Situation

Debugging network requests in iOS is painful because developers often need to inspect backend API problems or parameter structures, and they rely on external or in‑app tools.

External Debugging Tools

Charles – the classic proxy that supports simulator and device debugging with

map remote

and

map local

, but it requires the iPhone and Mac to be on the same Wi‑Fi and stops working when the computer is offline.

Surge – a newer proxy that works after installing a certificate, offers

rewrite

and

script

features, yet it still captures all apps' traffic and makes filtering difficult.

In‑App Debugging Frameworks

GodEye – provides full request monitoring but is no longer maintained and interferes with the app’s own requests.

Bagel – minimally invasive but requires a companion macOS app and can duplicate requests when a custom

URLProtocol

is used.

Network‑Debugging Principles

The standard iOS networking stack is built on

URLConnection

(legacy) and

URLSession

(modern). Both rely on

URLProtocol

objects to handle the actual transport protocols (http, https, ftp, etc.). By registering a custom subclass with

registerClass(_:)

, developers can insert their own protocol into the array that the system queries for each request.

<code>open class func canInit(with request: URLRequest) -> Bool</code>

When the system finds a class that returns

true

, it creates an instance and forwards all loading, receiving, and callback work to it, while the original protocol (http/https) remains untouched.

Problems with Traditional Approaches

Most in‑app solutions re‑create a new

URLSession

for every request, which leads to performance overhead, loss of the original session configuration, and inability to reuse sessions. Moreover, re‑sending the request can break multipart uploads (e.g., large image uploads with Alamofire) because the body is lost.

Introducing a Demux Manager

To reuse sessions, a Demux component stores a single

URLSession

per configuration and forwards callbacks based on a request identifier. This eliminates the need to create a new session for each request and preserves timeout, cache, and other settings.

<code>class LoggerURLSessionDemux { /* stores configuration, session, and task‑info map */ }</code>

DelegateProxy Solution

Even with Demux, the request still needs to be re‑sent. The final solution leverages RxSwift’s

DelegateProxy

concept: a proxy object becomes the

URLSessionDelegate

, forwards every delegate call to the original delegate, and simultaneously records the traffic. Because the original delegate never changes, the network flow is untouched.

<code>public final class URLSessionDelegateProxy: NSObject {
    private var _forwardTo: URLSessionDelegate?
    // forward all selector checks and delegate methods
}</code>

The proxy is injected by swizzling

URLSession(configuration:delegate:delegateQueue:)

and replacing the supplied delegate with an instance of

URLSessionDelegateProxy

. All delegate methods (data, task, stream) are forwarded using

objc_msgSend

to keep Objective‑C runtime compatibility, which solves edge cases such as React‑Native’s

RCTMultipartDataTask

that implements

NSURLSessionStreamDelegate

but declares

NSURLSessionDataDelegate

.

Full Implementation Highlights

Base logger protocol that stores the original

URLSessionTask

and overrides

canInit

,

canonicalRequest

,

startLoading

, and

stopLoading

.

Demux manager that returns a shared

LoggerURLSessionDemux

for a given session configuration.

Custom

LoggerURLProtocol

that creates a recursive request, registers it with the demux, and logs the request/response.

Delegate proxy that forwards every selector dynamically, preserving original behavior while capturing logs.

Result

The final approach captures all network traffic, works in both debug and production (including TestFlight), does not interfere with multipart uploads, and avoids the pitfalls of re‑sending requests. A simple UI can then display the logged requests, as shown in the screenshots.

iOSnetwork debuggingRxSwiftDelegateProxyURLProtocol
Jike Tech Team
Written by

Jike Tech Team

Article sharing by the Jike Tech Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.