When used correctly, debugging tools can help developers save a massive amount of time. Consider, for instance, the most basic breakpoint: it enables us to halt the execution of a program in order to analyze it step-by-step. Now just think how long that process would take if you had to implement it manually, by adding outputs to the console at every line. Things would get quickly out-of-hand and unmanageable.
As it turns out, the regular breakpoints that most of us are accustomed to are nothing but the tip of the iceberg, and Xcode has a whole collection of handy tools designed to speed up debugging workflows.
Perform action and do not halt execution
Let’s start with something most of us have already experienced several times: we set a breakpoint, when it is triggered we type a commande like
po myVariable, and then we resume the execution. Of course, this manual operation can become very tedious if we need to perform it several times with the same command. Fortunately, it’s quite simple to automate it:
- Right click on the breakpoint > “Edit Breakpoint…”
- Click on “Add Action”
- Select “Debugger Command”
- Input your command
- Check “Automatically continue after evaluating actions”
From now on, whenever this breakpoint is triggered, the custom debugger command will be executed and the execution will resume immediately after, thus removing any need for manual actions. That’s a lot of time saved 👍
Sometimes we want a breakpoint to be triggered only if a certain condition is met. Once again, thanks to the “Edit Breakpoint…” action it is very easy to set such a condition:
In this example the condition is quite simple, but more complicated expressions can be handled without any trouble, as it is possible to use boolean operators (
!, …) or perform function calls.
Conversely, there are times when we want a breakpoint to halt the execution only from the second time it is triggered. Think, for example, of functions that are called a first time when an object is created, then called again when an event occurs. If we are only interested in debugging the second kind of calls, it would be nice to filter out the first.
Once again, using “Edit Breakpoint…” the option is already waiting for us:
When an exception or an error is thrown and is not catched, the application crashes and the callstack gets printed to the console. This output is often very helpful to identify the source of the error, but it does not really allow to inspect the context where the error was thrown.
That’s when a new kind of breakpoint comes into play. Until now, all the breakpoints we have used were in some way anchored to our code, meaning that they were explicitly set on a specific line of the application.
In order to set a breakpoint that is triggered when an error occurs, we can make use of symbolic breakpoints, meaning breakpoints that are not linked to a particular line, but rather to a well-defined event, in our case the occurence of an error.
To do so, Xcode already offers two flavor of such breakpoints, depending on wether you are interested in Swift or Objective-C errors:
The great thing is that you can also edit them, in order to make them even more tailored to your needs, by filtering for a specific
Swift.Error type, or triggering either when the error is thrown or when it is catched.
Exception and error breakpoints are a specific kind of symbolic breakpoints, but it’s also possible to define much more generic ones.
For instance, it is possible to create a breakpoint that will be triggered whenever
viewDidLoad() is called on a controller:
This can be a very helpful tool when you know that a certain method is being called (for instance, a modal controller gets dismissed), and you are trying to pinpoint the source of this call.
When you think about it, breakpoint are constructs that focus on the code of an application. But an application is made of both code and data, so it could certainly be useful to have a breakpoint equivalent that would observe how data changes.
As it happens, such a construct is already available in Xcode, albeit it is not as visible as breakpoints are, and it’s called a watchpoint.
To set a watchpoint on a variable, you first have to set a breakpoint that will halt the execution in a memory frame where the variable is reachable. Then you just need to right-click the variable, and select “Watch”:
Alternatively, it’s also possible to create the watchpoint through the debugging console:
watchpoint set variable self.counter.
Then the breakpoint can be disabled, and the watchpoint will be triggered whenever the value of the variable changes.
Breakpoints and watchpoints are incredibly powerful features and, when used correctly, they can help speed-up the debugging process of an application in a very significant manner.
This article did not aim at being an exhaustive list of all the features breakpoints implement, so please feel free to read more about this topic. A good place to start being the command-line syntax to interact with the debugger.