The debugging toolkit your iOS app deserves.
Network inspector. Storage browser. Log viewer. Rules engine. QA checklist. One floating bubble.
Documentation • Quick Start • Install • API • License
Network • Logs • Storage • Rules • QA
Drop one line into your debug build and get a complete debugging suite — no Charles Proxy, no separate logging dashboard, no manual environment switching. Just shake or tap the bubble.
Noober.shared.start() // That's it.Using AI to code? Point your AI assistant (Claude, Cursor, Copilot, etc.) to
AI_INTEGRATION.md— a machine-readable reference with exact API signatures, copy-paste integration patterns, and constraints. It's designed so any AI can integrate Noober into your project in seconds.
|
Captures every |
Rewrite URLs to redirect traffic between servers. Mock responses to test edge cases without a backend. Intercept requests mid-flight — inspect, edit, then proceed or cancel. Five match modes: Host, Contains, Prefix, Exact, Regex. Build rules by hand in the debugger — those persist across launches — or ship them with the app via |
|
Register your environments once, switch with one tap. Noober rewrites matching requests automatically. Supports multiple base URLs per environment (API + CDN + WebSocket). Active selection persists across launches. |
Define test items with priority and associated endpoints. Mark pass/fail, attach network requests to failures, track progress. Build-aware — auto-resets when a new build is detected. Share reports. |
|
Browse and edit UserDefaults with type-aware parsing. View Keychain items with lazy-loaded values. Export UserDefaults as JSON. See app info at a glance. |
Structured logs with four levels ( |
|
Test any URL scheme or universal link without leaving the app. Input a URL, tap Fire, see if it opened or failed. Save favorites for reuse. Full history with result badges, timestamps, and persistence across launches. |
Zero dependencies — pure Swift, no third-party libraries. One-line setup — |
Xcode → File → Add Package Dependencies:
https://github.com/noob-programmer1/Noober-iOS.git
Package.swift:
dependencies: [
.package(url: "https://github.com/noob-programmer1/Noober-iOS.git", from: "1.0.0")
],
targets: [
.target(name: "YourApp", dependencies: ["Noober"])
]Warning
Noober is for debugging only. Always wrap with #if DEBUG.
#if DEBUG
import Noober
#endif
@main
struct MyApp: App {
init() {
#if DEBUG
Noober.shared.start()
#endif
}
var body: some Scene {
WindowGroup { ContentView() }
}
}#if DEBUG
import Noober
#endif
@main
struct MyApp: App {
init() {
#if DEBUG
// Switch between servers with one tap
Noober.shared.registerEnvironments([
.init(name: "Production", baseURL: "https://api.example.com"),
.init(name: "Staging", baseURL: "https://api.staging.example.com",
notes: "Uses test payment keys"),
.init(name: "Local", baseURL: "http://localhost:8080"),
])
// QA checklist for the current build
Noober.shared.registerChecklist([
.init("Login flow", notes: "Test email + social",
priority: .high, endpoints: ["/auth/login"]),
.init("Checkout", notes: "With & without saved cards",
priority: .high, endpoints: ["/api/payments"]),
.init("Pull-to-refresh on feed", priority: .normal),
])
// Mocks that ship with the build — flip them on from the debugger
Noober.shared.registerMocks([
.init("Empty cart", url: "/api/cart", json: #"{"items": []}"#,
isEnabled: false),
.init("Payment failure", url: "/api/payments", method: "POST",
statusCode: 500, json: #"{"error": "gateway_timeout"}"#,
isEnabled: false),
])
// Pause requests for review before they hit the network
Noober.shared.registerIntercepts([
.init("Payments", url: "/api/payments", method: "POST", isEnabled: false),
])
// Shortcuts in the debugger's Storage tab
Noober.shared.registerActions([
.init("Clear Cache", icon: "trash", group: "Storage") {
CacheManager.shared.clearAll()
},
.init("Reset Onboarding", icon: "arrow.counterclockwise") {
UserDefaults.standard.removeObject(forKey: "hasSeenOnboarding")
},
])
Noober.shared.start()
#endif
}
var body: some Scene {
WindowGroup { ContentView() }
}
}Noober.shared.log("User signed in")
Noober.shared.log("Payment failed", level: .error, category: .init("payments"))
Noober.shared.log("Cache miss", level: .debug, category: .init("cache"))Noober — Main singleton
@MainActor
public final class Noober {
public static let shared: Noober
public var isStarted: Bool { get }
public func start()
public func stop()
public func showDebugger()
public func hideDebugger()
public func registerEnvironments(_ environments: [NooberEnvironment])
public func registerChecklist(_ items: [QAChecklistItem])
// Rules shipped with the build. Registering replaces the previously
// registered set, so calling on every launch never duplicates. Rules
// created by hand in the debugger are untouched and take precedence.
public func registerMocks(_ mocks: [NooberMock])
public func registerIntercepts(_ intercepts: [NooberIntercept])
// Add or remove a single rule mid-session. Added rules go on top.
@discardableResult public func addMock(_ mock: NooberMock) -> UUID
@discardableResult public func addIntercept(_ intercept: NooberIntercept) -> UUID
public func removeMock(id: UUID)
public func removeIntercept(id: UUID)
// Debugger shortcuts. `register` replaces, `add` appends.
public func registerActions(_ actions: [CustomAction])
public func addAction(_ action: CustomAction)
public func addActions(_ actions: [CustomAction])
public func removeAction(_ title: String)
public func clearActions()
// Custom URLSession setups (Alamofire, etc.)
nonisolated public func inject(into configuration: URLSessionConfiguration)
// Manual screen names for custom routers. Thread-safe.
nonisolated public func trackScreen(_ name: String)
// Thread-safe — call from any thread
nonisolated public func log(
_ message: String,
level: LogLevel = .info,
category: LogCategory = .general,
file: String = #file,
line: UInt = #line
)
}NooberEnvironment — Server environment definition
public struct NooberEnvironment: Identifiable, Codable, Sendable, Hashable {
public let id: UUID
public let name: String
public let baseURLs: [String]
public let notes: String
// Single base URL
public init(name: String, baseURL: String, notes: String = "")
// Multiple base URLs (positional mapping)
public init(name: String, baseURLs: [String], notes: String = "")
}QAChecklistItem — Test item definition
public struct QAChecklistItem: Sendable {
public let title: String
public let notes: String
public let priority: QAChecklistPriority // .high, .normal, .low
public let endpoints: [String]
public init(
_ title: String,
notes: String = "",
priority: QAChecklistPriority = .normal,
endpoints: [String] = []
)
}NooberMock — Canned response definition
public struct NooberMock: Sendable {
public init(
_ name: String,
url: String,
match: NooberURLMatch = .contains, // .host, .contains, .prefix, .exact, .regex
method: String? = nil, // nil matches any method
statusCode: Int = 200,
headers: [String: String] = ["Content-Type": "application/json"],
body: Data? = nil,
isEnabled: Bool = true,
id: UUID = UUID()
)
// Same, with a JSON string body
public init(_ name: String, url: String, ..., json: String, ...)
}Registered mocks are not persisted — the app re-creates them on every launch,
so editing the source is the only way to change them. Register with
isEnabled: false to have a mock sit in the debugger switched off, ready for a
tester or an AI agent to flip on.
NooberIntercept — Pause-for-review definition
public struct NooberIntercept: Sendable {
public init(
_ name: String,
url: String,
match: NooberURLMatch = .contains,
method: String? = nil,
isEnabled: Bool = true,
id: UUID = UUID()
)
}Register with isEnabled: false unless you want matching requests paused from
launch — a rule that stops every payment call will stall the app.
CustomAction — Debugger shortcut
public struct CustomAction: Sendable {
public init(
_ title: String,
icon: String = "bolt.fill", // SF Symbol
group: String = "", // optional section header
handler: @escaping @Sendable @MainActor () -> Void
)
}LogLevel & LogCategory
public enum LogLevel: String, CaseIterable, Sendable, Comparable {
case debug = "DEBUG"
case info = "INFO"
case warning = "WARN"
case error = "ERROR"
}
public struct LogCategory: RawRepresentable, Hashable, Sendable {
public init(_ rawValue: String)
public static let general: LogCategory
}| Layer | What it does |
|---|---|
| URLProtocol swizzling | Injects NetworkInterceptor into URLSessionConfiguration.default and .ephemeral. Captures all HTTP/HTTPS traffic automatically. |
| WebSocket swizzling | Hooks into URLSessionWebSocketTask to capture sent/received frames, connection status, and close codes. |
| Screen tracking | Swizzles UIViewController.viewDidAppear(_:) to tag each request with the source screen. |
| Rules engine | Evaluates mock → intercept → environment → rewrite rules in order. Mock/intercept short-circuit. Rules persist in UserDefaults. |
| Overlay windows | Bubble lives in a UIWindow at .alert + 1. Debugger at .alert + 2. Custom hit testing passes through non-bubble touches. |
| Thread safety | @MainActor for all stores. nonisolated for logging. os_unfair_lock for screen tracker. NSLock for rule snapshots read by the interceptor. |
Full API docs with guides:
noob-programmer1.github.io/Noober-iOS
Built with Swift-DocC. Source in Sources/Noober/Noober.docc/.
Build docs locally
# Build the DocC archive
xcodebuild docbuild \
-scheme Noober \
-destination 'generic/platform=iOS' \
-derivedDataPath .derivedData
# Transform for static hosting
$(xcrun --find docc) process-archive \
transform-for-static-hosting \
.derivedData/Build/Products/Debug-iphoneos/Noober.doccarchive \
--hosting-base-path Noober-iOS \
--output-path docsDeploy by pushing docs/ to the gh-pages branch. The .nojekyll file in the branch root prevents Jekyll from interfering with DocC's SPA routing.
| Minimum | |
|---|---|
| iOS | 13.0+ |
| Swift | 6.0+ |
| Xcode | 16+ |
| Dependencies | None |
Apache 2.0 — License
Built by Abhishek Agarwal




