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.
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 remoteand
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
rewriteand
scriptfeatures, 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
URLProtocolis used.
Network‑Debugging Principles
The standard iOS networking stack is built on
URLConnection(legacy) and
URLSession(modern). Both rely on
URLProtocolobjects 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
URLSessionfor 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
URLSessionper 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
DelegateProxyconcept: 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_msgSendto keep Objective‑C runtime compatibility, which solves edge cases such as React‑Native’s
RCTMultipartDataTaskthat implements
NSURLSessionStreamDelegatebut declares
NSURLSessionDataDelegate.
Full Implementation Highlights
Base logger protocol that stores the original
URLSessionTaskand overrides
canInit,
canonicalRequest,
startLoading, and
stopLoading.
Demux manager that returns a shared
LoggerURLSessionDemuxfor a given session configuration.
Custom
LoggerURLProtocolthat 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.
Jike Tech Team
Article sharing by the Jike Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.