How to Take UI Debugging to the Next Level With LLDB

Evaluating statements, finding memory addresses, and more

Adam Wareing
Better Programming

--

Photo by the author.

In case you aren’t already familiar, LLDB is the debugger tool used in LLVM. If you have ever done iOS or Mac development, you will know it as the debugging tool in the console.

When I first started iOS development, my tech lead demonstrated how to po an object. In other words, to just print the value of a variable. For a long time, that’s all I used it for and that’s all I thought it could do. However, I recently discovered it is far more powerful than I had ever imagined.

Have you ever had a constraint violation where you couldn’t figure out what screen or UIView was affected? Have you ever wanted to debug properties of a visual component at runtime, but you weren’t able to? Well, you’re not alone. I often found myself wanting to investigate the custom properties of a view, layer, or animation properties and not being able to due to the limited functionality of Xcode’s view hierarchy debugger. Well, if you want to find out you can debug these, read on.

Writing Basic Expressions in LLDB

Before diving in too deep, I will quickly cover how to write an expression in Swift using LLDB. Firstly, you want to pause code execution by either using the pause button in Xcode, clicking the view debug hierarchy, or hitting a breakpoint. You begin writing an expression by using the expression or e keyword followed by -- to write your command. As this tutorial is going to be in Swift, we are going to use an optional -l flag to set the language to Swift, as shown below:

(lldb) expression -l Swift -- print("Hello world")// Or for shorthand abbreviate `expression` to `e`.(lldb) e -l Swift -- print("Hello world")

It’s important to note that Foundation is the only library imported at runtime, so if we want to use something from another library (e.g. UIKit, which we will use later on), then we will want to import that:

(lldb) e -l Swift -- import UIKit

How to Find Memory Addresses of UI Components

As we are using a command-line debugger, we have to first get the memory address of the UI component we want to inspect. This may be a broken constraint or a view that has its memory address already printed in the console, like below. Alternatively, you may want to get it from the view hierarchy debugger or a manually printed statement.

Mutating and Inspecting the UI at Runtime

In Swift, there is a function called unsafeBitCast(_:to:). When given a memory address and class type, it can return the object.

Now that we have a reference to the object, we can do any of the following basic reads or writes from the object. Note that if you mutate the UI, you must resume execution so the main thread can make the appropriate changes.

We can also use it to view and modify constraints to help with fixing violations at runtime:

Or to interact with your own views by importing your app’s main target:

Summary

This is an incredibly powerful tool that allows you to do anything you usually would, but at runtime. I found that this has the most impact when used with a view controller that requires a lot of overhead to navigate to (e.g. at the end of a sign-up flow). By understanding the runtime state with the ability to mutate and validate changes, you can minimize the overhead in re-compiling and navigating back to the screen to verify changes.

Hopefully, this has helped to make UI debugging easier for you!

--

--