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 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.
open class func canInit(with request: URLRequest) -> BoolWhen 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.
class LoggerURLSessionDemux { /* stores configuration, session, and task‑info map */ }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.
public final class URLSessionDelegateProxy: NSObject {
private var _forwardTo: URLSessionDelegate?
// forward all selector checks and delegate methods
}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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
