WWDC 2018 Session 412 : Advanced Debugging with Xcode and LLDB
preface
In the career of programmer writing bug, only bug will always accompany you, how to deal with the relationship between bug, is a required course for every programmer. Entry-level programmers, in particular, are often affected by bugs, which can lead to acne, weight gain and hair loss.
Every iOS and macOS developer is lucky because Apple’s Xcode and LLDB debugger tools, which every developer should use, help us solve problems faster. This article will focus on Xcode’s breakpoint debugging, LLDB debugger, and UI Hierarchy. These tips will greatly reduce the number of recompilations during debugging and reduce your wait time.
These techniques are so simple to use and so useful in a development scenario that every developer needs to master them.
Improve Swift Debugging Reliability
1.1 Failed to Get module from AST Context
For many developers using Swift, debugging can be a pain in the neck. For example, when LLDB reconstructs the compile state of the AST Context, it may not be able to detect some module changes in complex cases, so the debugger prompts Failed to get module from the AST Context.
In Xcode 10, to address this problem, a new Expression Evaluator is created for the current frame call stack.
1.2 Swift Type Resolution (Swift Type Resolution)
Some developers may encounter problems such as the inability to display variable types and print variable information during debugging, as shown below:
Apple tracks down a large number of bug reports and has fixed this bug in Xcode 10, which will no longer appear in debugging messages.
Tips and Tricks
2.1 Configure Behaviors to Dedicate a TAB for Debugging
If you’re looking at code and you’re forced to switch to the page where the breakpoint is, the experience of switching between the breakpoint page and the previous page is very poor. Now you can set to automatically create a new TAB when the breakpoint is broken. By switching tabs you can quickly and easily cut back to the previous page.
Set automatic New Debug Tab: Xcode -> Behaviors -> Edit Behaviors… Pauses -> interface -> ✅ Show Tab Name Tab Name in active window.
2.2 LLDB Expressions Can modify program State
In LLDB, you can use the expression command to change the current status of the program. The abbreviations e and expr can also achieve the same function. For example, we use a simple UILabel. We set the value hello to myLabel. Normally, myLabel on a view should display Hello.
func test() -> Void {
myLabel.text = "hello"// breakpoint ->}Copy the code
You can set a breakpoint after mylabel.text = “hello”, run the program and change the value of the breakpoint by typing the following expression in the LLDB debugger on the console. After continuing to run the program, you will see hello World on the interface.
// Change myLabel text expr myLabel. Text ="hello world"
Copy the code
In addition to changing the value of mylabel.text, you can do the same in LLDB as you would in Xcode. For example, you can use expressions to change the color of its text, as shown in the following code, or you can execute a function.
Expr myLabel.textColor = uIColor. red // executetestMethods exprtest(a)Copy the code
2.3 Use auto-continuing breakpoints with debugger Commands to inject code live
Instead of changing App state directly from the console via the LLDB debugger, you can do the same by adding commands to breakpoints. And it is more convenient to set debugging commands through breakpoints, almost real-time insertion of code function.
Set a Breakpoint by editing Breakpoint… Open the edit box and you can fill the Action with different debugging commands in sequence to achieve the same functionality as before. You can also check Automatically continue after evaluationg Actions to continue subsequent code without stopping on this line.
2.4 Print function arguments (” Po $arg1″ ($arg2, etc) in assembly frames to print function arguments)
First, let’s look at global Breakpoints. You can click on the + sign in the lower left corner of the Breakpoints Navigator and then choose Symbolic Breakpoint… In the Symbol column, you can type in any function you want to listen on such as [UILabel setText:], and then all objects of UILabel type on all pages will execute this breakpoint when setting the text property. (PS: I’m not the coolest 😎)
In the breakpoint console, variable properties and other information are not displayed. How do we know what is set? Next we can print out the information we want with $arg1, $arg2, etc.
$arg1 is the object itself, $arg2 is the function on which the object is called, Po cannot print the function name directly, it needs to be added (SEL), and $arg3 is the value assigned to text.
2.5 Create dependent breakpoints using breakpoint set –one-shot true
Breakpoint set –one-shot true Breakpoint set –one-shot true breakpoint set –one-shot true After this command is executed once, it will be automatically deleted.
The cool thing is, we’re going to create a breakpoint, as shown below, and let this breakpoint do all of this, using a breakpoint to create another one-time breakpoint, and to make the whole process feel neutral, I recommend Automatically continue after evaluationg Actions.
What exactly does this breakpoint do? When the breakpoint at line 61 in the figure is executed, the breakpoint does not cause command execution to pause. All it does is create a global breakpoint by using the breakpoint set –name “[UILabel setText:]” command. Add –one-shot true to indicate a one-time breakpoint.
Breakpoint set –one-shot true –name “[UILabel setText:]” breakpoint set –one-shot true –name “[UILabel setText:]” So the next line mylabel. text = “Hello world” is not paused.
2.6 Skip lines of code by dragging Instruction Pointer or “Thread jump –by 1”
First let’s look at how to skip code by dragging and dropping an instruction pointer. As shown in the figure below, directly drag the button pointed by the red arrow to start execution from where. If you drag it up, the previous code can be repeated, while if you drag it down, the code skipped in the middle will not be executed.
We skipped 2 lines of code by using thread jump –by 2. The following image will print only 1 and 4.
Use watchpoints when variables are modified by using watchpoints
We have described using global breakpoints and one-time breakpoints to listen for property changes on the [UILabel setText:] function. Another option is to use watchpoints to listen for property changes by monitoring memory changes.
We can set a breakpoint in viewDidLoad and then go to the console to find the properties you want to listen for, as shown below:
After selecting the attribute you want to monitor, right click to pop up the window below. Click Watch “Count” to monitor the change of the attribute count, for example, execute count+=1. Note that watchpoints need to be reset whenever the pointer changes after recompilation.
Evaluate obj-c code in Swift frames with “expression -l objc-o –“
In daily debugging, it is convenient to print the page view structure with the LLDB command Po [self.view recursiveDescription]. However, when we use this command in Swift call stack, we will print the following error:
po self.view.recursiveDescription()
error: <EXPR>:3:6: error: value of type 'UIView? ' has no member 'recursiveDescription'
self.view.recursiveDescription()
~~~~~^~~~ ~~~~~~~~~~~~~~~~~~~~
Copy the code
In fact, we can use the “expression -l objc-o –” command to print the view structure we want using the obj-c code. Remember to add the ‘symbol on both sides of self.view.
expression -l objc -O -- [`self.view` recursiveDescription]
Copy the code
The command alias
expression -l objc -o — creates an alias for this command, and then uses the alias to use the action.
Alternatively, we can use the Po unsafeBitCast(< PSTR >, unsafePointer.self) command to print the object description, center point coordinates, and of course set the related properties.
// Print object (LLDB) Po unsafeBitCast(0x7fe439d13160, uilabel. self) <UILabel: 0x7FE439d13160; frame = (57 141; 42 21); text ='Label'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003942a30>> // Print center point coordinates (LLDB) Po unsafeBitCast(0x7FE439d13160, uilabel.self). Center ▿ (78.0, 151.5) -x: 78.0-y: 151.5 // Set the center point coordinate (LLDB) Po unsafeBitCast(0x7FE439d13160, uilabel.self).centerCopy the code
2.9 Flush view changes to the screen using “expression” By running the “expression catransaction.flush ()” command CATransaction. Flush () “)
You can change the COORDINATES of the UI from the console via the LLDB debugger, but you won’t immediately see any changes to the page. In fact you did change its value, you just need to use “expression catransaction.flush ()” to refresh your page.
Used in conjunction with the command to modify the UI coordinate values, you can see something exciting happening in your emulator.
UnsafeBitCast (0x7fe439d13160, uilabel.self).center. Y = 300Copy the code
2.10 Add custom LLDB commands using aliases and scripts Add custom LLDB commands using aliases and scripts
As you learn more about LLDB commands and use them more effectively, you may find that the small console limits your ability and you need a bigger stage.
Now I’m going to show you how to use Python scripts to execute commands. You’ll need to download nudge. Py, a Python script prepared by apple’s developers that helps you move UI controls easily and quickly. We need to place the nudge. Py file in your user root directory ~/nudge.
Next we need to create a ~/. Lldbinit file in the user root directory and add the following command and alias:
command script import ~/nudge.py
command alias poc expression -l objc -O --
command alias 🚽 expression -l objc -- (void)[CATransaction flush]
Copy the code
Having done that, we can use our custom command nudge x-offset y-offset [view] as follows:
// Nudge (LLDB)command script import ~/nudge.py
The "nudge" command has been installed, type "help nudge" forPo myLabel ▿ Optional<UILabel> -some: <UILabel: 0x7fc04a60fff0; frame = (57 141; 42 21); text = 'Label'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; Layer = <_UILabelLayer: 0x600001D36c10 >> // Y upward offset 5 (LLDB) nudge 0-5 0x7fc04a60fff0
Copy the code
The effect of adjusting the position of the controls in the emulator:
2.11 LLDB Print Commands (LLDB Print Commands)
Command | Alias For | Steps TO Evaluate |
---|---|---|
po <expression> |
expression –object-description — <expression> |
1. Expression: evaluate 2. Expression: debug description |
p | expression — | 1. Expression: evaluate 2. Outputs LLDB-formatted description |
frame variable | none | 1. Reads value of from memory 2. Outputs LLDB-formatted description |
The p and Po commands output object and LLDB data respectively in terms of aliases and execution procedures.
The difference with frame variable is that the value is fetched from memory in the current frame call stack. Only variables are accepted as arguments, not expressions. Using the frame variable command, you can print all the variables in the current frame call stack.
Xcode Advanced View Debugging
3.1 Quickly Locating a View in the Debug Navigator
During the development, we often use Debug View Hierarchy to check the current page View structure. Normally, the UI nested Hierarchy of the navigation bar is very large, which makes it impossible to find the Hierarchy of the control we want to View quickly and accurately.
Navigate to Reveal in Debug Navigator by clicking on the navigate control you want to view and then clicking on the navigate option.
3.2 Displaying View Clipped Content
When we encounter such a bug, we can use Debug View Hierarchy to check the current View. When entering the Debug page, you will see the following situation:
I think my label should be complete, but the exceeded page was cut out. At this time, I need to confirm whether the fact is the same as I think. As shown below, we need to enable the Show Clipped Content option.
Finally, I saw that the truth was consistent with my guess, and I could accurately formulate a solution based on the real situation.
3.3 Viewing Auto Layout Debugging
To View Constraints on a control in Debug View Hierarchy, simply enable the Show Constraints option, and selecting any control shows the Constraints it has.
You can view the details of the constraint in the Object Inspector in the right pane.
Creation backtraces in the inspector
In debug mode, we have a way to see the creation call stack for each control and each constraint, which helps us quickly locate the source of the problem. For example, I manually constrain the top distance of 100 for my label.
let myLabelTopConstraint = myLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100)
NSLayoutConstraint.activate([myLabelTopConstraint])
Copy the code
After running the Demo, enable Debug View Hierarchy. After enabling the display constraint option, you can find the constraint and select it. In the object inspector’s Backtrace column on the right side, you can see a list of call stacks. Click the small arrow on the right to jump to the code that created the object.
This feature needs to be enabled manually by clicking on the project Target -> Edit Scheme… -> Run -> Diagnostics -> Logging -> Check Malloc Stack and switch to All Allocation and Free History mode to enable this feature.
3.5 Access Object Pointers (Copy Casted Expressions)
In view debug mode, we sometimes need to enter expressions in the LLDB debugger to change the position of the control.
Let’s say we want to modify a constraint, we first need to get the pointer to the constraint. The good news is that Xcode is very convenient to allow us to invoke the constraint, invoke the direct shortcut to ⌘ + C and ⌘ will be copied to the console.
You can print a Description of the constraint, just like the Description in the right sidebar inspector.
/ / Po Po + copy good pointer ((NSLayoutConstraint *) 0 x600000dd4460) / / output < x600000dd4460 NSLayoutConstraint: 0 UILabel:0x7fdb1c70a710'WWDC 2018: Improving efficiency by exploding Xcode and... '.top == UIView:0x7fdb1c70b950.top + 100 (active)>
Copy the code
You may also want to review the previous content, modify the values of the constraints, and refresh the page to see what the emulator looks like when you’re done.
// Set constraint to 200 (LLDB) e [((NSLayoutConstraint *) 0x600000DD4460)setConstant:200] // Refresh UI // 🚽 is expression-lObjc -- (void)[CATransaction flush] alias of the command (LLDB) 🚽Copy the code
3.6 ⌘-click to select the blocked view (⌘-click-through for selection)
In debugging, if the view you want to select is blocked by another view, you can select the view on the back through 3D view mode, as shown below.
But it’s hard to be elegant, and there are tricky angles that can make your head spin. The right way to ⌘ to click directly to the obscured view is to ⌘ to 2D. Go ahead and check.
4. Debugging Dark Mode
4.1 Switching dark mode (Appearance overrides)
With macOS 10.14 and Xcode 10 installed, you can use Dark Mode in development. You can find a small black and white box button at the bottom of Xcode. By selecting this button, you can switch between Dark and Light emulators. If your Macbook has a Touch Bar, you can also use the buttons on the Touch Bar to switch.
In the StoryBoard you can find View as: Light/Dark Appearance at the bottom to preview the Dark and Light Appearance.
If you select any View in macOS development, you will find the Appearance property in the inspector in the right pane. This property allows you to set a fixed Appearance color for the View and its subviews, without changing color as the user switches between Dark and Light Appearance.
4.2 Capturing Active Mac Apps (Capturing Active Mac Apps)
Our UI Hirerachey can only display the contents of one UIWindow at a time, so when we’re debugging, the pop-up UIWindow doesn’t show up with the UI structure inside the page, like UIAlertView, the pop-up UIWindow doesn’t show up with the UI structure inside the page.
So if we want to view popup UIWindow, we need to close all the current file structure on the left side and close it up, and then you’ll see that there’s another UIWindow under the UIWindow that the ViewController is in, Once checked, you can view the UI hierarchy of the pop-up UIWindow.
4.3 Check the Named colors and NSAppearance details in Inspector
During UI Hierarchy debugging, we can check Dark Mode related information in the inspector in the right bar, and select a UILabel to check the Text Color attribute of the label. There are 3 types of colors in Dark Mode:
- System Color: Indicates the recommended System Color. You can adjust the text Color based on the current appearance Color.
- Named Color: The Named Color should be set by the developer in Assets Catalog. You can set different Color values for Dark Light.
- Custom RGB color: Purely manually set custom RGB fixed color values.
The Text Color in the following figure is the Named Color set in Assets Catalog. The Named Color is titleColor. You can set the appropriate name for this setting according to the scene.
In the View column below, below the inspector, we can find Appearance and Effective properties. Appearance is a fixed Appearance color selection that indicates that the subview of the View cannot be switched, and Effective is the current Appearance color in effect.
Set Named Color in Assets Catalog:
conclusion
The powerful LLDB, especially in conjunction with BreakPoint, allows us to imagine more, and the increasingly useful UI Hirerachey makes debugging more flexible. It may take some time to learn, but I believe that mastering these techniques will save you a lot of time.
You don’t have to worry about finding a bug before you leave work anymore. Use it early, call it a day, and work until 3pm at most. I hope this article will be helpful to every reader.
Refer to the link
- WWDC 2018 Session 412 – Advanced Debugging with Xcode and LLDB
- WWDC 2018 Session 412 – Advanced Debugging with Xcode and LLDB
For more WWDC 18 articles, head over to the xSwiftGG WWDC 18 Topics directory