Debugging in iOS

jair moreno
Wizeline Mobile
Published in
6 min readApr 26, 2021

Get the best out of the LLDB in Xcode

Photo by Tania Melnyczuk on Unsplash

Debugging is one of the most essential processes for developers while developing any kind of program. It allows you to detect and remove existing and potential bugs. Thereby, iOS development is not an exception.

Like many of you, my first approach to the amazing world of debugging was using the very well-known printstatement. However, this way of debugging can be overwhelming, and your code can get cluttered with multiple lines of print statements.

Example of print statement

Breakpoints

But sometimes, when trying to debug something, we want to see what is going on in every step of our program. Of course, we can do it, and this is where breakpoints make their appearance. A breakpoint is a point in our program where the code will stop executing. In Xcode, we can use LLDB (low-level debugger), the default debugger for developers on Apple platforms, and make awesome things that we did not even imagine.

Breakpoint

po Command

After we set a breakpoint and our program’s execution stops, we can use commands to get all the information we need. The most common command is probably po, which is an alias for expression -- object-description —-command. This command gives us the object description that we are inspecting. In the following example, we print a textual representation of an instance of our type, in this case of a Book.

We have a default object description that the system runtime provides us, but we can create our own description using the protocol CustomDebugStringConvertible and using the variable debugDescription. Example:

CustomDebugStringConvertible

And po command can do even more than that. It can compute practically any expression that you would compile in your program. Look at the following example, where we are using the same book instance but this time, we calculate how many days passed since the book’s launch.

Executing code with po command

p Command

The second way that we have to print variables is with the command p, which is the alias for expression -- . The information we get in result with this command is basically the same as po but, as you can imagine, this time the object is printed without description. The main difference is that pmakes a process under the hood that podoes not. This process is called Dynamic Type Resolution, which I’ll explain with an example:
In Swift, the static representation of a type in the code can be different from the dynamic type at runtime. In this example, we can see that the static type of the instance maria is Human , but at runtime, this variable will have an instance of type Student. If we try to print the instance maria using p , we get an instance of type Student, because p is computing the result’s dynamic type; it gives us a more accurate type for a given variable.

po vs p

However, it won’t be possible to access the properties of the instance without making a cast.

Casting instance to access its properties

v Command

There is a third way to print variables, which is using the v command. The output of the command is the same as p , but the main difference relies on that the v command does not compile code, which makes it very fast. Taking the previous example, with v , we don’t need to make a cast because it reads the values of an instance from memory and then uses the dynamic resolution multiple times to make a better interpretation.

v command

So, in my opinion, I would use this commands in the following scenarios:

· po if you want to compile your code and get the object description.

· p if you only want to compile your code

· v if you want to print variables in the current stack frame

Custom Breakpoints

Now it is time to talk about the power of breakpoints. Let’s see a more practical example with a simple app. This app calculates the total of a check adding the tip percentage, and then prints the result, but as you can see, there is a bug in it.

Bug 🐛

The result we expect is, of course $1100. Because we wanted to add a 10% tip to the check of $1000, but the total says $1000. Let’s use a breakpoint to see what is going on.

code executed after tapping Calculate button

At this point you can see that we are printing the amount instead of the amount + tipValue. But because we don’t want to make this change, compile, and then rerun the app to make sure we are correct, we can edit our breakpoint and make our change on the fly.

  1. We double click the breakpoint to edit it
  2. We add the following expression: expression amount = amount + tipValue
  3. We click the option Automatically continue after evaluating actions. This triggers the expression and continues the execution of the program.
Custom breakpoint

Now we tap the Calculate button and we see that the total amount is printed correctly.

First bug is fixed ✅

Issue fixed

Modify Our View Using the Debugger

Now we have a second requirement. We wanted to move our Calculate button and total label (which are both in a stack view) 100 points down to the bottom.

Vertical space to be modified

Of course, now that we are debug-developers, we want to do this with the console as well. To do that, we need to have the memory address of the stack, we could use the code visual view debugger to get that address, but we can also use the console!

To get all the info related to the child views contained in our view controller’s view we will use this new command: expression -l objc -O -- [`self.view` recursiveDescription] .

With the -l objc -O we are telling the debugger that we are about to run Objective-C code.

Get the address of the stack view to be modified

With the result, we can easily find the address for the stack view.

The next thing we are going to do is to inspect the description of this stack view by using a function called unsafeBitCast. Then, we can access to all the properties of our stack view.

printing the frame of the stack

Having all this freedom, we can simply update the frame of our stack. We are adding 100 points to the y value of the frame.

Modifying the frame of the stack view

Wait a minute, nothing happened 🙃. This is because after any UI changes on the debugger we need to manually trigger a render to update our view using the class method flush() of the CATransaction class.

flush in action!

We have now finished the second requirement ✅

As you saw, the LLDB is a very powerful tool that can help us as developers to boost our productivity and improve our times while debugging code, fixing issues or even just having fun discovering everything that we can do without recompiling and rerunning our app several times. 🤓

P.S. Don’t forget to add your bug fixes in the code. 😄

--

--