Xcode Debugging Tips And Tricks — WWDC 2018
Debugging is an integral part of the entire software development process. A good programmer should have efficient debugging skills in order to write bug free code in the long run.
Through this article lets get familiarized with Xcode debugging through LLDB commands which avoid the necessity of recompiling the code for every modification.
Tip 1: Expression command
Utilizing expression command efficiently and configuring breakpoint in such a way that recompiling code is not at all necessary.
If you want to evaulate any code whenever breakpoint is hit, use expression command in the Xcode console. In the Xcode console type below command with the code you want to execute.
expression 'Code line to be evaluated'
Breakpoint can also be configured to get this done automatically by adding the same expression command inside edit breakpoint tab.
Use expression command to inject code in-between the debugging. In the below example orginal value of text is ‘Business news section’. Using expression command in the console text string can be modified.
When to use?
If you need to view more complex data or change program data, you can use the general “expression” command. It takes an expression and evaluates it in the scope of the currently selected frame while debugging.
Tip 2: Symbolic breakpoints
Symbolic breakpoints are very powerful which can be added against any method whose name matches the breakpoint symbol. Developers do not need to concern about method’s signature or which module it belongs to. Add the method name for which breakpoint to be triggered in the symbol field of the breakpoint as shown in the below example.
Objective C format methods like [UILable setText:] get triggered when its name added in symbolic breakpoints. In the below example breakpoint is triggered at the line.
cell.title.text = getTextForEachSection
In this case when breakpoint is hit, we will start looking at assembly code because we don’t have source code for UIKitCore.
You don’t have to get scared if you are in assembly code of the system frameworks as we can inspect arguments passed into the setText function using pseudo — registers. pseudo — registers are provided by debugger to see arguments held by registers.
- $arg1 — We can see reciever of the objective c message
- (SEL) $arg2 — Selector. (SEL) is necessary as LLDB doesn’t know types of these arguments.
- $arg3: First parameter passed into the function
When to use?
Fixing auto layout warnings in the console like UIViewAlertForUnsatisfiableConstraints and layout related warnings like UICollectionViewFlowLayoutBreakForInvalidSizes.
Tip 3: Adding condition to breakpoints
There might be a case when symbolic break point is getting hit very frequently when more objects have symbols matching the breakpoint. We can avoid this by adding a condition which triggers breakpoint only if that condition is met.
Tip 4: One shot breakpoint
A temporary breakpoint which only exists until its triggered and then its automatically deleted. This can be used to activate any other breakpoint after one shot break point has been hit.
breakpoint set --one-shot true --name "-[UILabel setText:]"
In the above example symbolic breakpoint ‘setText’ is activated only after one shot breakpoint has been hit.
Tip 5: Skip execution of code line to ease the debugging.
In many cases we want to save the debugging time by skipping some of the code. We can do this by asking debugger to skip over the code.
- Drag the grab handle near the breakpoint thread 1(instruction pointer) to the code in which you are interested or
- Configure the breakpoint to skip the code line by adding an expression
thread jump --by (number of lines to be skipped)
Tip 6: Print custom debug description for any object.
We can customise debug description for any class objects using CustomDebugConvertible protocol.
Tip 7: Watch breakpoint
This is used to watch or keep an eye on any variable when its value is set.
In the above example we have set a watch break point on person age variable. Whenever age variable is set in the code, watch breakpoint will get triggered.
Before we proceed to layout and constraint debugging lets first cover following points
Objective c mode commands
Custom debugging file
Objective C mode commands
Some of the APIs are not public in swift and can only be used for debugging process. Objective C being a dynamic language, these APIs can be called in Objective C mode.
Lets say you have a simple view hierarchy and need to print debug description for the views. This is an alternative to Xcode’s visual 3D mode debugging.
- expression -l objc — to inform that debugger expression should be evaluated in objective c mode even-though its on swift frame.
- O — This is same as po to print the description.
- [‘self.view’ recursiveDescription] — Raw expression input.
Lets say some of the commands are being used frequenlty we can make typing them easy through aliasing.
An alternative way to get the debug description for an object if its memory address is known. This is unsafe because its up to you to provide the correct object type.
unsafeBitcast(x: T, to: U.type)
The good thing about unsafebitcast is that it returns a typed result. Using the typed result we can call its property names and methods and also modify them.
In order to reflect any UIElement layout update on frames inside view buffer (screen) CATransaction.flush() command should be used.
Custom debugging file
As LLDB is scriptible using python where you get full access to all the LLDB APIs. Developers can create their own python file to customize the debugging commands as per their need. We can import that file to Xcode’s console and start typing our custom debugging commands.
Download Apple’s .py file for reference — https://developer.apple.com/sample-code/wwdc/2018/UseScriptsToAddCustomCommandsToLLDB.zip
Layout and Constraint Debugging
In order to fix any layout or constraint related issue, its necessary to have memory address. If we have an outlet for any UI element then we can easily access its memory address through debug description. In other case when we don’t have an outlet then memory address can be accessesd through following ways.
- Through view recursive description, find memory address of all the subviews.
- To print debug description and get type result, use unsafeBitcast command.
3. Modify the frame or size of UI element which is causing the issue.
4. In order to reflect those changes on the screen, inform the debugger by typing the command expression CATransaction.flush()
5. We can also update the constaints in debug mode without compiling code again and again.
6. Update the constraints by accessing their memory address in Xcode visual debugging mode. Then update it through expresion command and finally call expression CATransaction.flush() command to update the frames in viewbuffer.
Above techniques can be a huge timesaver for complex projects and crucial for hard to reproduce bugs.