Xcode and LLDB Advanced Debugging Tutorial: Part 1
One of the very intriguing sessions carried out by some of Apple’s finest debugging engineers during 2018’s WWDC was Advanced Debugging with Xcode and LLDB. They informed us about some impressive tips and tricks on how to utilize Xcode’s breakpoints and low-level debugger (LLDB) to optimize the debugging process whenever it happens that developers encounter bugs and all out to fix them.
In this 3 parts tutorial, I’ll walk you through most of what has been concluded in that WWDC session. I created a demo project specifically to elaborate on how to use different types of breakpoints alongside the LLDB to fix bugs in your project/application.
Demo Project
I developed a project of conventional tasks that most of the iOS developers out there have definitely dealt with at some point. It’s important to learn about its features/rules before proceeding with this article. Here’s what the demo project is all about:
- A table view controller that loads a list of posts when opened for the first time.
- The table view controller supports loading more posts when reaching the bottom end of the table view.
- The number of times the user is allowed to load posts is restricted to 7.
- The user is able to reload new posts via a refresh controller (pull down to refresh).
- There are two labels on the navigation bar that do indicate how many posts have been retrieved (right label) and how many times have the user loaded posts (left label).
You can download it from here if you’re an Objective-C player.
Swift player? Download it from here.
Xcode and run it! 😉
Bugs to Fix!
Now that you’re ready with your project, you might have noticed the following bugs:
- Pull down to refresh does not reload new posts.
- The user is not receiving an alert (via an alert controller) whenever the HTTP request fails due to connection problems.
- The user is allowed to load posts more than 7 times.
- The left navigation bar label that indicates how many times the user did load posts is not being updated.
Golden Rule: For the rest of this article, you’re not to stop the compiler or re-run the application after running it for the very first time. You’re fixing the bugs at runtime.
The Power of Expression
Let’s tackle the first bug.
1. Pull down to refresh does not reload new posts.
Here are the steps to reproduce it:
✦ Run the application → the first 10 posts are loaded.
✦ Scroll down to load more posts.
✦ Scroll up to the top of the table view, and pull down to refresh.
✦ New posts are not reloaded and the old posts still exist & the posts count is not reset.
A typical approach to fix this bug is to investigate what happens inside the selector method that is assigned to the dedicated UIRefreshControl of the table view controller. Head to PostsTableViewController
and navigate to the section with the pragma mark Refresh control support
. We can deduce from thesetupRefreshControl
function that the selector dedicated for the refresh control is the reloadNewPosts
function. Let’s add a breakpoint at the first line of this function and debug exactly what’s going on in there. Now scroll to the top of the table view and pull down to refresh it.
The debugger has paused at the breakpoint you did set once you have released the refresh control. Now to further explore what happens next, tap on the debugger step over button.
Now we have a clear idea what wrong is going on !!
The if statement condition is not satisfied (i.e theisPullDownToRefreshEnabled
boolean property is set to NO
) and hence the equivalent code for reloading the posts is not executed.
The typical approach to fix this is to stop the compiler, set the isPullDownToRefreshEnabled
property to YES
/true
and that would do it. But it’s more convenient to test such a hypothesis before implementing some actual changes to the code and without the need to stop the compiler. Here come the breakpoint debugger command actions of expression statements really handy.
Double tap on the set breakpoint, or right click, edit breakpoint and tap on the “Add Action” button. Select “Debugger Command” action.
Now what we want to do is set the isPullDownToRefreshEnabled
property to YES
/true
. Add the following debugger command.
Objective-C
expression self.isPullDownToRefreshEnabled = YES
Swift
expression self.isPullDownToRefreshEnabled = true
The next thing you should do is checking the “Automatically continue after evaluating actions” box. This will cause the debugger not to pause at the breakpoint for each and every-time it gets triggered and automatically continue after evaluating the expression you just added.
Now scroll to the top of the table view and pull down to refresh.
voilà new posts have been retrieved, replacing the old ones, and hence the posts count got updated.
As you’ve just resolved the first bug, pick up your anti-bugs weapons and proceed to the second one.
2. The user is not receiving an alert (via an alert controller) whenever the HTTP request fails due to connection problems.
Here are the steps to reproduce this one:
✦ Turn off your iPhone’s/Simulator’s internet connection.
✦ Scroll to the top of the table view, and pull down to refresh.
✦ No new posts are loaded due to network error.
✦ A network error alert controller is not presented to the user.
Head to PostsTableViewController and navigate to the section with the pragma mark Networking
. It only includes one function which is loadPosts
. It utilizes a shared instance of a networking manager to execute a GET HTTP request that returns an array of posts object via a “success” completion handler or an instance of NSError
via a “failure” completion handler.
What we want to do is to add some code inside the failure completion handler to present a networking error alert controller. If you navigated to the section with the pragma mark Support
, you’ll find that there’s an already implemented function presentNetworkFailureAlertController
that does handle the presentation of the required alert controller. All that we need to do is to call that function inside the loadPosts
failure completion handler.
The conventional way is to stop the compiler, add the required line of code and you’re done. Let’s go for the unconventional!
Add a breakpoint inside the failure completion handler below the line
Objective-C
[self updateUIForNetworkCallEnd];
Swift
self.updateUIForNetworkCallEnd()
Double tap on the set breakpoint, tap on the “Add Action” button. Select debugger command action. Add the following debugger command
Objective-C
expression [self presentNetworkFailureAlertController]
Swift
expression self.presentNetworkFailureAlertController()
Check the “Automatically continue after evaluating actions” box.
With your internet connection disabled, scroll to the top of the table view and pull down to refresh or you can scroll down to the bottom of the table view in an attempt to load more posts. Here’s what you get 🎉🎉
What you just did was “injecting” a line of code with an expression statement implemented as a debugger command action inside a dedicated breakpoint.
Recap
Let me just recap what we did with breakpoints debugger command action expression statements:
- Manipulate an existing property value.
- Injecting a new line of code.
Both tasks were achieved at runtime. We didn’t really need to stop the compiler, modify things and then re-run the application.
Where to go?
Check out the second part of this tutorial to fix extra bugs and learn about a special type of breakpoints, that is watchpoints.