A toolkit to make debugging iOS applications easier π
DebugSwift is a comprehensive toolkit designed to simplify and enhance the debugging process for Swift-based applications. Whether youβre troubleshooting network issues, monitoring WebSocket connections, optimizing performance, or testing push notifications, DebugSwift provides a powerful set of features to make your debugging experience more efficient. β¨ New: Unified Network Inspector with WebSocket support π Swift 6 ready with strict concurrency checking<!/div> |
---|
.shared
pattern across all singletonsLibraries List | Class Explorer | Class Details |
---|---|---|
Comprehensive network traffic monitoring with unified HTTP and WebSocket inspection:
HTTP Requests | WebSocket Connections | Frame Timeline |
---|---|---|
Add the following dependency to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/DebugSwift/DebugSwift.git", from: "1.0.0")
]
Then, add "DebugSwift"
to your targetβs dependencies.
import DebugSwift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let debugSwift = DebugSwift()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
#if DEBUG
debugSwift.setup()
debugSwift.show()
#endif
return true
}
}
extension UIWindow {
open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
super.motionEnded(motion, with: event)
#if DEBUG
if motion == .motionShake {
// Assuming you have a reference to your DebugSwift instance
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.debugSwift.toggle()
}
}
#endif
}
}
If your app uses shared app group containers (for extensions, widgets, etc.), you can configure them for debugging:
#if DEBUG
// Configure app groups for file browser access
DebugSwift.Resources.shared.configureAppGroups([
"group.com.yourcompany.yourapp",
"group.com.yourcompany.widgets"
])
debugSwift.setup()
debugSwift.show()
#endif
Note: App groups are automatically detected from your appβs entitlements if not manually configured.
import DebugSwift
class ChatManager {
private var webSocketTask: URLSessionWebSocketTask?
func connect() {
let url = URL(string: "wss://chat.example.com/websocket")!
webSocketTask = URLSession.shared.webSocketTask(with: url)
// Optional: Register with custom channel name for better organization
DebugSwift.WebSocket.register(task: webSocketTask!, channelName: "Chat")
webSocketTask?.resume()
// β
Connection automatically monitored in DebugSwift!
startListening()
}
func sendMessage(_ text: String) {
let message = URLSessionWebSocketTask.Message.string(text)
webSocketTask?.send(message) { error in
if let error = error {
print("Send failed: \(error)")
}
}
// β
Sent message automatically captured with method swizzling!
// No manual logging needed
}
private func startListening() {
webSocketTask?.receive { result in
switch result {
case .success(let message):
// Handle received message
// β
Received message automatically captured!
self.handleMessage(message)
self.startListening() // Continue listening
case .failure(let error):
print("Receive failed: \(error)")
}
}
}
private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
switch message {
case .string(let text):
print("Received text: \(text)")
case .data(let data):
print("Received data: \(data.count) bytes")
@unknown default:
print("Unknown message type")
}
}
}
// β¨ That's it! Full WebSocket monitoring with zero configuration
// All frames, connections, and status changes are automatically tracked
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let debugSwift = DebugSwift()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
#if DEBUG
// Configure network monitoring
DebugSwift.Network.shared.ignoredURLs = [
"https://analytics.example.com" // Ignore analytics endpoints
]
// Set request threshold monitoring
DebugSwift.Network.shared.setThreshold(100, timeWindow: 60.0)
DebugSwift.Network.shared.setRequestBlocking(true)
// Setup memory leak detection
DebugSwift.Performance.shared.onLeakDetected { leakData in
print("π΄ Memory leak detected: \(leakData.message)")
// Send to analytics or logging service
}
// Enable push notification testing
DebugSwift.PushNotification.enableSimulation()
// Setup and show DebugSwift
debugSwift.setup()
debugSwift.show()
#endif
return true
}
}
// Add custom debugging actions
DebugSwift.App.shared.customAction = {
[
.init(title: "Development Tools", actions: [
.init(title: "Clear User Data") {
UserDefaults.standard.removeObject(forKey: "userData")
print("β
User data cleared")
},
.init(title: "Simulate Network Error") {
// Trigger a test network error
self.simulateNetworkError()
},
.init(title: "Test WebSocket Reconnection") {
self.chatManager.reconnect()
}
]),
.init(title: "Feature Flags", actions: [
.init(title: "Enable Beta Features") {
FeatureFlags.shared.enableBetaFeatures()
}
])
]
}
// Add custom development information
DebugSwift.App.shared.customInfo = {
[
.init(title: "Environment Info", infos: [
.init(title: "API Environment", subtitle: Configuration.apiEnvironment),
.init(title: "Feature Flags", subtitle: FeatureFlags.shared.enabledFlags.joined(separator: ", ")),
.init(title: "Database", subtitle: CoreDataManager.shared.storeURL.lastPathComponent)
]),
.init(title: "User Session", infos: [
.init(title: "User ID", subtitle: UserSession.shared.userId ?? "Not logged in"),
.init(title: "Session Token", subtitle: UserSession.shared.hasValidToken ? "Valid" : "Invalid")
])
]
}
To enable APNS device token tracking in DebugSwift, integrate the following code into your AppDelegate:
import DebugSwift
import UserNotifications
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Your existing DebugSwift setup
#if DEBUG
debugSwift.setup().show()
#endif
// Request push notification permissions
requestPushNotificationPermissions()
return true
}
private func requestPushNotificationPermissions() {
Task { @MainActor in
let center = UNUserNotificationCenter.current()
// Inform DebugSwift that we're about to request permissions
DebugSwift.APNSToken.willRequestPermissions()
do {
let granted = try await center.requestAuthorization(options: [.alert, .badge, .sound])
if granted {
UIApplication.shared.registerForRemoteNotifications()
} else {
DebugSwift.APNSToken.didDenyPermissions()
}
} catch {
DebugSwift.APNSToken.didFailToRegister(error: error)
}
}
}
// MARK: - Push Notification Delegate Methods
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Register with DebugSwift for debugging
DebugSwift.APNSToken.didRegister(deviceToken: deviceToken)
// Your existing token handling code here
let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("π± Device token: \(tokenString)")
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
// Register failure with DebugSwift
DebugSwift.APNSToken.didFailToRegister(error: error)
// Your existing error handling code here
print("β Failed to register: \(error.localizedDescription)")
}
}
// Get current device token
let token = DebugSwift.APNSToken.deviceToken
// Check registration state
let state = DebugSwift.APNSToken.registrationState
// Get APNS environment
let environment = DebugSwift.APNSToken.environment
// Copy token to clipboard programmatically
let copied = DebugSwift.APNSToken.copyToClipboard()
// Refresh registration status
await DebugSwift.APNSToken.refreshStatus()
When debugging push notification flows, developers often need the exact device token to:
DebugSwift eliminates the need for manual token logging and provides a convenient interface for accessing and copying tokens during development.
If you want to ignore specific URLs, use the following code:
DebugSwift.Network.shared.ignoredURLs = ["https://reqres.in/api/users/23"]
If you want to capture only a specific URL, use the following code:
DebugSwift.Network.shared.onlyURLs = ["https://reqres.in/api/users/23"]
Adjust the URLs in the arrays according to your needs.
Configure request rate limiting to prevent API abuse and monitor network usage:
// Basic threshold configuration
DebugSwift.Network.shared.threshold = 100 // 100 requests per minute
DebugSwift.Network.shared.enableRequestTracking()
// Advanced configuration with custom time window
DebugSwift.Network.shared.setThreshold(50, timeWindow: 30.0) // 50 requests per 30 seconds
// Configure alert settings
DebugSwift.Network.shared.setThresholdAlert(
emoji: "π¨",
message: "Too many requests!"
)
// Enable request blocking when threshold is exceeded
DebugSwift.Network.shared.setRequestBlocking(true)
// Set endpoint-specific limits
DebugSwift.Network.shared.setEndpointThreshold(
"api/users",
limit: 50,
timeWindow: 60.0
)
// Monitor current request count
let currentCount = DebugSwift.Network.shared.getCurrentRequestCount()
print("Current requests: \(currentCount)")
// View breach history
let breaches = DebugSwift.Network.shared.getBreachHistory()
for breach in breaches {
print("Breach at \(breach.timestamp): \(breach.message)")
}
All threshold configurations are automatically persisted using UserDefaults.
When the threshold is exceeded, youβll see:
Monitor WebSocket connections in real-time with comprehensive frame inspection:
// WebSocket monitoring is AUTOMATIC with method swizzling
// No manual registration required - just create and use WebSocket as normal
let task = URLSession.shared.webSocketTask(with: url)
task.resume()
// β
Connection automatically detected and monitored in DebugSwift!
// Send messages - automatically logged
task.send(.string("Hello WebSocket!")) { error in
if let error = error {
print("Send failed: \(error)")
}
}
// β
Sent frames automatically captured and displayed
// Create WebSocket with custom channel name for organization
let webSocketURL = URL(string: "wss://api.example.com/websocket")!
let task = URLSession.shared.webSocketTask(with: webSocketURL)
// Optional: Register with custom channel name for better organization
DebugSwift.WebSocket.register(task: task, channelName: "Live Updates")
// Start the connection
task.resume()
// Send messages - automatically monitored with method swizzling
task.send(.string("Hello WebSocket!")) { error in
if let error = error {
print("Send failed: \(error)")
}
}
// β
Sent frames automatically captured
// Receive messages - automatically monitored
task.receive { result in
switch result {
case .success(let message):
// Handle message - automatically logged in DebugSwift
self.handleMessage(message)
case .failure(let error):
print("Receive error: \(error)")
}
}
// β
Received frames automatically captured
// Enable/disable WebSocket monitoring
DebugSwift.WebSocket.enableMonitoring()
DebugSwift.WebSocket.disableMonitoring()
// Check monitoring status
let isEnabled = DebugSwift.WebSocket.isMonitoringEnabled
// Get connection statistics
let activeConnections = DebugSwift.WebSocket.activeConnectionCount
let totalUnreadFrames = DebugSwift.WebSocket.totalUnreadFrames
// Data management
DebugSwift.WebSocket.clearAllData()
DebugSwift.WebSocket.clearFrames(for: webSocketURL)
The WebSocket Inspector provides:
Perfect for debugging:
Test push notifications without setting up a push notification service or server. Perfect for development and testing scenarios.
// Enable push notification simulation
DebugSwift.PushNotification.enableSimulation()
// Simple notification
DebugSwift.PushNotification.simulate(
title: "New Message",
body: "You have a new message"
)
// Detailed notification with all options
DebugSwift.PushNotification.simulate(
title: "Special Offer! π",
body: "Get 50% off your next purchase",
subtitle: "Limited time offer",
badge: 1,
sound: "default",
userInfo: ["type": "marketing", "discount": "50"],
delay: 5.0 // Show after 5 seconds
)
// Use predefined templates
DebugSwift.PushNotification.simulateFromTemplate("Message")
DebugSwift.PushNotification.simulateFromTemplate("News Update", delay: 3.0)
// Quick convenience methods
DebugSwift.PushNotification.simulateMessage(from: "John", message: "Hey, how are you?")
DebugSwift.PushNotification.simulateReminder("Meeting at 3 PM", in: 60.0)
DebugSwift.PushNotification.simulateNews(headline: "Breaking: New iOS version released", category: "Technology")
DebugSwift.PushNotification.simulateMarketing(title: "Flash Sale!", offer: "50% off everything", discount: "50")
// Run comprehensive test scenarios
DebugSwift.PushNotification.runTestScenario(.messageFlow) // 3 message-related notifications
DebugSwift.PushNotification.runTestScenario(.newsUpdates) // News, sports, weather updates
DebugSwift.PushNotification.runTestScenario(.marketingCampaign) // Welcome, cart reminder, flash sale
DebugSwift.PushNotification.runTestScenario(.systemAlerts) // Security, backup, update notifications
// Create custom scenarios
let customNotifications = [
SimulatedNotification(title: "Step 1", body: "First notification"),
SimulatedNotification(title: "Step 2", body: "Second notification"),
SimulatedNotification(title: "Step 3", body: "Final notification")
]
DebugSwift.PushNotification.runTestScenario(.customFlow(customNotifications))
// Simulate user interactions
DebugSwift.PushNotification.simulateInteraction(identifier: "notification-id")
DebugSwift.PushNotification.simulateForegroundNotification(identifier: "notification-id")
DebugSwift.PushNotification.simulateBackgroundNotification(identifier: "notification-id")
// Add custom templates
let customTemplate = NotificationTemplate(
name: "Custom Alert",
title: "System Alert",
body: "{{message}}",
sound: "alarm",
userInfo: ["type": "system"]
)
DebugSwift.PushNotification.addTemplate(customTemplate)
// Get all templates
let templates = DebugSwift.PushNotification.templates
// Remove template
DebugSwift.PushNotification.removeTemplate(id: "template-id")
// Configure notification behavior
var config = DebugSwift.PushNotification.configuration
config.showInForeground = true // Show notifications while app is active
config.playSound = true // Enable notification sounds
config.showBadge = true // Show badge numbers
config.autoInteraction = false // Automatically interact with notifications
config.interactionDelay = 3.0 // Delay before auto-interaction
config.maxHistoryCount = 100 // Maximum notifications to keep in history
DebugSwift.PushNotification.updateConfiguration(config)
// Get notification history
let history = DebugSwift.PushNotification.history
// Clear all history
DebugSwift.PushNotification.clearHistory()
// Remove specific notification
DebugSwift.PushNotification.removeNotification(id: "notification-id")
The push notification simulator provides:
Perfect for testing:
The DebugSwift floating button provides real-time feedback about your appβs network activity:
The floating button count combines both HTTP requests and active WebSocket connections, giving you a comprehensive view of your appβs network activity at a glance.
Configure shared app group containers for file system debugging across app extensions and related apps:
// Configure app group identifiers for file browser access
DebugSwift.Resources.shared.configureAppGroups([
"group.com.yourcompany.yourapp",
"group.com.yourcompany.shared"
])
// Add individual app groups
DebugSwift.Resources.shared.addAppGroup("group.com.yourcompany.widgets")
// Remove specific app groups
DebugSwift.Resources.shared.removeAppGroup("group.com.yourcompany.old")
// Get accessible app group containers
let accessibleGroups = DebugSwift.Resources.shared.getAccessibleAppGroups()
for (identifier, url) in accessibleGroups {
print("App Group: \(identifier) at \(url.path)")
}
If no app groups are configured, DebugSwift will automatically:
com.apple.security.application-groups
The Files browser will show a segmented control allowing you to switch between:
DebugSwift.App.shared.customInfo = {
[
.init(
title: "Info 1",
infos: [
.init(title: "title 1", subtitle: "title 2")
]
)
]
}
DebugSwift.App.shared.customAction = {
[
.init(
title: "Action 1",
actions: [
.init(title: "action 1") { [weak self] in // Important if use self
print("Action 1")
}
]
)
]
}
DebugSwift.App.shared.customControllers = {
let controller1 = UITableViewController()
controller1.title = "Custom TableVC 1"
let controller2 = UITableViewController()
controller2.title = "Custom TableVC 2"
return [controller1, controller2]
}
If you prefer to selectively disable certain features, DebugSwift can now deactivate unnecessary functionalities. This can assist you in development across various environments.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let debugSwift = DebugSwift()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
#if DEBUG
debugSwift.setup(
// Main features - WebSocket is now part of .network
hideFeatures: [
.network, // Includes both HTTP and WebSocket inspectors
.resources,
.performance,
.interface,
.app
],
// Swizzle features - Fine-grained control over monitoring
disable: [
.network, // HTTP request monitoring
.webSocket, // WebSocket connection monitoring
.location, // Location simulation
.views, // UI view debugging
.crashManager, // Crash report collection
.leaksDetector, // Memory leak detection
.console, // Console log capture
.pushNotifications // Push notification simulation
]
)
debugSwift.show()
#endif
return true
}
}
Get the data from memory leaks in the app.
DebugSwift.Performance.shared.onLeakDetected { data in
// If you want to send data to some analytics
print(data.message) // Returns the name of the class and the error
print(data.controller) // If is a controller leak
print(data.view) // If is a view leak
print(data.isDeallocation) // If is a deallocation of leak (good for false/positive)
}
Harness the Power of Visual Information within the iOS Hierarchy Tree to Uncover Intricate Layouts and Element Relationships in Your Application.
Simply press and hold the circle button to reveal the Snapshot and Hierarchy for a comprehensive overview.
Enhance your understanding by pressing and holding on a specific view to reveal information such as:
DebugSwift
is now a class, not an enum.shared
pattern// Before (old version)
DebugSwift.setup()
DebugSwift.show()
DebugSwift.App.customInfo = { ... }
DebugSwift.Network.ignoredURLs = [...]
// After (Swift 6 version)
let debugSwift = DebugSwift()
debugSwift.setup()
debugSwift.show()
DebugSwift.App.shared.customInfo = { ... }
DebugSwift.Network.shared.ignoredURLs = [...]
uploadProgress
In the AppDelegate
.
class AppDelegate {
let debugSwift = DebugSwift()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
debugSwift.setup()
debugSwift.show()
// Call this method
DebugSwift.Network.shared.delegate = self
return true
}
}
And conform with the protocol:
extension AppDelegate: CustomHTTPProtocolDelegate {
func urlSession(
_ protocol: URLProtocol,
_ session: URLSession,
task: URLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64
) {
Session.default.session.getAllTasks { tasks in
let uploadTask = tasks.first(where: { $0.taskIdentifier == task.taskIdentifier }) ?? task
Session.default.rootQueue.async {
Session.default.delegate.urlSession(
session,
task: uploadTask,
didSendBodyData: bytesSent,
totalBytesSent: totalBytesSent,
totalBytesExpectedToSend: totalBytesExpectedToSend
)
}
}
}
}
Thank you for visiting our project! If you find our work helpful and would like to support us, please consider giving us a β star on GitHub. Your support is crucial for us to continue improving and adding new features.
Every star counts and makes a difference. Thank you for your support! π
Our contributors have made this project possible. Thank you!
Contributions are welcome! If you have suggestions, improvements, or bug fixes, please submit a pull request. Letβs make DebugSwift even more powerful together!
DebugSwift is licensed under the MIT License - see the LICENSE file for details.