My first thought after learning about LLDB was ‘damn I wish I knew this before.’ In this article I will discuss 3 simple debugging features that have helped me breeze through bug fixes – po, expression, and the instruction pointer.
First let’s talk about po. Most of the time my debugging consisted of setting a breakpoint where I was parsing downloaded JSON. To me simply being able to view the JSON in the variable tree, instead of having to create print or NSLog statements was amazing. However for for more complex apps it was becoming difficult to find the variable I was looking for in the variables tree.
This is where po comes in:
po <variable_name>
po means ‘print out’ and what this will do is print the variable’s default debugDescription value. We can also create a custom debug message that po will print by using: CustomDebugStringConvertible.
Alternatively there is the p command which will print the default LLDB formatted result providing the memory address, and all properties.
po and p are great when you simply want to view a variable, but what if you would like to change the variable’s value?
Being able to po variables was great for finding the source of bugs. But once I found them and put in a fix I would need to rebuild and run my app. Instead we can use expression to modify our variables while the app is running.
expression <instruction>
In the example above the we print the object’s name once which prints ‘AJ.’ In between the print statements we can use expression to change the name variable. The second time it prints we can see the name has changed.
I’ve found this especially useful when debugging UI, by using expression to change colors, and label text without having to rebuild my app for every little change.
Using expressions is really easy when the values we are changing are held by an instance variable. However if values are hardcoded we start to run into some problems. In this example we try to override some hardcoded values:
We can use expression to call createAndPrintSomeClassAtZero with a different name but the hardcoded name will also still print.
We’ll need to do a little extra work to tackle this problem.
The instruction pointer is the hamburger icon highlighted in green. We can drag it to any line we want and the app will move to that point of execution. When we try to move it we will see a message pop up:
This makes sense because when we move the point of execution it can break things. Since this skips over parts of our code, variables that are called later might never be initialized which will cause a crash. If done correctly though we can use this to skip over unwanted parts of our code.
Using the breakpoint above we can use the same expression we used earlier, and then move the point of execution from line 40 to line 41, continue the app and viola:
A more real world example would be downloading content from a server. During development our server might have some issues and be timing out for one of our API calls. Instead of waiting for the timeout to happen we can skip over the call completely (in this example I used a fatal error for demonstration purposes). The example below simulates a long download task which would call printDownloadString if it was successful.
Since this will be a recurring problem until the server is fixed we can set a breakpoint that runs automatically. By right clicking on the breakpoint and clicking edit we can add actions to run during the breakpoint.
This breakpoint will move the instruction pointer from line 44 to line 50 which skips the download completely. The second debugger command will then call printDownloadString directly. We check off ‘Automatically continue…’ so that the breakpoint doesn’t pause the app. After running we can see that the breakpoint handled everything for us.
LLDB is a powerful way to debug your app without changing any of your source code and introducing even more issues. By using po, expression, and instruction pointer together with breakpoints you can debug issues quickly, without having to rebuild and run your app.