Chasing Invisible Code: Exploring Breakpoints in LLDB

I used to think breakpoints were… boring.
You drop one on a line, run the app, execution pauses, you inspect stuff, move on. That was the entire mental model.
But then I stumbled into something weird: what if the code you want to observe isn’t yours?
Like deep inside UIKit. Or buried in some framework. Or triggered indirectly where you don’t even know which line to put a breakpoint on.
That’s when LLDB started feeling less like a debugger… and more like a search engine for running code.
The Moment It Clicked
I was trying to understand when viewDidLoad actually fires across different view controllers.
Not just mine. All of them.
And I realized: I don’t care about lines of code. I care about a function being called.
That’s where symbolic breakpoints come in.
Instead of saying “pause here,” you say:
“Pause whenever this thing is called.”
For example: -[UIViewController viewDidLoad]
That’s a symbol. And LLDB can hook into it.
More on symbolic breakpoints in Xcode.
Hunting for Symbols
Before setting a breakpoint, you sometimes need to confirm the symbol exists.
LLDB gives you a way to search for it:
(lldb) image lookup -n "-[UIViewController viewDidLoad]"
The -n flag tells LLDB to look for a function or symbol by name.
This command basically answers:
“Hey LLDB, where does this thing live in memory?”
Once you know it exists, you’re ready to trap it.
Setting Your First Symbolic Breakpoint
The simplest form:
(lldb) b -[UIViewController viewDidLoad]
And just like that, your app will pause every time any view controller loads.
Not just yours. Everything.
That’s when it starts to feel powerful… and slightly dangerous.
When Typing Gets Annoying: Regex Breakpoints
Typing full symbol names can get painful pretty quickly.
Imagine something like:
SomeModule.SomeClass.name.setter : Swift.Optional<Swift.String>
Yeah… no thanks.
This is where regex breakpoints feel like cheating.
Instead, you can do:
(lldb) rb SomeClass.name.setter
Or go even broader:
(lldb) rb name.setter
Now LLDB will stop at anything matching that pattern.
It’s like saying:
“I don’t know exactly what I’m looking for, but I’ll know it when I see it.”
Going Wide: Breakpoint Scope
Then I discovered you don’t even have to target a specific function.
You can target an entire file:
(lldb) rb . -f ListViewController.swift
This sets breakpoints on:
- getters and setters
- functions and methods
- closures and blocks
Basically everything in that file.
Or even a whole framework:
(lldb) rb . -s UIKitCore
Now you’re stepping into Apple’s world.
You can also filter by language:
(lldb) breakpoint set -L swift -r . -s SomeLibrary
Or something more experimental:
(lldb) breakpoint set -A -p "if let"
That last one blew my mind a bit.
It pauses execution anywhere "if let" appears.
Not always practical… but definitely fun to try.
Breakpoints That Do Things
Then comes the part that made me pause for a second.
Breakpoints don’t just stop execution. They can run commands.
For example:
(lldb) breakpoint set -n "-[UIViewController viewDidLoad]" -C "po $arg1" -G1
Let’s unpack that:
-n→ target function-C→ command to executepo $arg1→ print the first argument (the view controller instance)-G1→ continue execution automatically
So now, instead of stopping, it logs every viewDidLoad call and keeps going.
You’ve basically turned a breakpoint into a logging system.
And without touching your code.
Cleaning Up
At some point, things get messy.
Too many breakpoints, too many triggers, too much chaos.
Thankfully:
(lldb) breakpoint delete
Clean slate.