A lightweight, passive network traffic monitoring tool for iOS/macOS development. Built with SwiftUI and URLProtocol, it captures and displays all HTTP/HTTPS requests made by your app without modifying existing network code.
- Passive Monitoring: Captures all URLSession requests without affecting normal network behavior
- Floating Ball UI: Draggable, non-intrusive floating button with expandable request list
- Request Details: View complete request/response headers, body (with JSON pretty-printing), status codes, and timing
- Export & Copy: Export individual request logs or long-press to copy details to clipboard
- Performance Optimized: Limits stored entries and body sizes to prevent memory issues
- Thread-Safe: Uses modern Swift concurrency with
@Observableand@MainActor
┌─────────────────────────────────────────────────────────┐
│ App Layer │
│ (Your Application) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ NetworkMonitorModule │
│ Public API for start/stop/clear │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ NetworkMonitorStore │
│ @Observable - Thread-safe state │
└─────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ NetworkMonitorCore │ │ FloatingBallView │
│ URLProtocol reg │ │ (SwiftUI UI) │
└─────────────────────┘ └─────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ NetworkMonitorURLProtocol │
│ Intercepts & records HTTP requests │
└─────────────────────────────────────────────────────────┘
core/Common/Source/NetworkMonitor/
├── Model/
│ ├── NetworkMonitorEntry.swift # Data model for a single request
│ └── NetworkMonitorConfiguration.swift # Configuration options
├── Core/
│ ├── NetworkMonitorCore.swift # URLProtocol registration
│ └── NetworkMonitorURLProtocol.swift # Request interception & forwarding
├── Store/
│ └── NetworkMonitorStore.swift # @Observable state management
├── UI/
│ ├── FloatingBallView.swift # Draggable floating button
│ ├── NetworkMonitorPanelView.swift # Main panel with request list
│ ├── RequestRowView.swift # Individual request row
│ ├── RequestDetailView.swift # Expandable request details
│ └── ShareSheet.swift # iOS share sheet wrapper
└── NetworkMonitorModule.swift # Public module entry point
- iOS 17.0+ / macOS 14.0+
- Swift 5.9+
- SwiftUI
The module is part of the Common framework. Ensure core/Common is included in your project build.
In your app entry point (AppDelegate or LTAppApp):
#if DEBUG NetworkMonitorModule.start() #endif
In your root view hierarchy:
@main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() #if DEBUG NetworkMonitorModule.floatingBall #endif } } }
The floating ball will appear in the bottom-right corner. Tap to open the request list.
// Start monitoring (typically in app launch) NetworkMonitorModule.start() // Stop monitoring NetworkMonitorModule.stop() // Clear all captured entries NetworkMonitorModule.clear() // Access the store directly if needed let entries = NetworkMonitorStore.shared.entries let isExpanded = NetworkMonitorStore.shared.isExpanded
let config = NetworkMonitorConfiguration( maxEntries: 100, // Maximum entries to store (default: 100) maxBodySize: 1_048_576 // Max body size in bytes (default: 1MB) ) // Pass configuration when creating store let store = NetworkMonitorStore(configuration: config)
- Draggable button in bottom-right corner
- Shows badge with request count
- Tap to expand/collapse panel
- Displays all captured requests in reverse chronological order
- Shows method badge (color-coded), URL path, status, and duration
- Tap to expand details, long-press to copy
- Export Button: Share complete request info via system share sheet
- Request Section: Headers + Body (JSON pretty-printed)
- Response Section: Status, Duration, Headers + Body
- Expandable/collapsible sections
NetworkMonitorURLProtocol is registered to URLSessionConfiguration.default.protocolClasses. It works by:
- Intercepting: The protocol's
canInit(with:)determines which requests to capture - Recording: Captures request metadata (URL, method, headers, body)
- Forwarding: Creates an ephemeral session to forward the actual request
- Recording Response: Captures response data as it streams back
- Storing: Saves complete request/response to
NetworkMonitorStore
@Observableprovides automatic thread-safe state management@MainActorensures UI updates happen on the main threadNetworkMonitorCoreusesNSLockfor safe registration/unregistration
- Post-Registration Only: Only captures requests made after
start()is called - URLSession Only: Does not capture requests made via other networking libraries unless they use URLSession internally
- Debug Builds Only: Should be wrapped in
#if DEBUGfor release builds
public struct NetworkMonitorEntry: Identifiable, Sendable { public let id: UUID public let url: URL public let method: String public let requestHeaders: [String: String] public let requestBody: Data? public var responseHeaders: [String: String]? public var responseBody: Data? public var statusCode: Int? public var error: Error? public let startTime: Date public var endTime: Date? }
entry.duration // TimeInterval? entry.state // .loading, .success, .failed entry.formattedDuration // "123ms" or "1.23s" entry.prettyPrintedRequestBody // JSON formatted entry.prettyPrintedResponseBody // JSON formatted entry.formattedExportText // Full export format entry.formattedCopyText // Copy-friendly format
- Start Early: Call
start()as early as possible in app lifecycle to capture initialization requests - Use Debug Guards: Always wrap monitoring code in
#if DEBUG - Large Bodies: Be aware of body size limits for very large responses
- Memory Management: The store automatically limits entries, but very large bodies may still consume memory
When adding features:
- Update this README with new functionality
- Update
docs/network_monitor_design.mdwith design details - Ensure thread safety with
@MainActorand proper locking - Add appropriate
@Observableconformance
Internal use - LittleThings