Ramble LLDB: The most elegant way to print the UIViewController life cycle

preface

There are obvious benefits to printing the UIViewController lifecycle. It’s easy to see which UIViewController the current page is going into, and to check if the current UIViewController is destroyed (dealloc) after exiting the current UIViewController. There are two common ways to do this. One is to override the lifecycle method in the parent class and print the corresponding method name with an NSLog or print method so that all business subclasses that inherit from it can automatically print the specified lifecycle method. The other option is to use Method Swizzling, a non-intrusive way to print all uiViewController-specified lifetimes. In my early development, I used to do Method swaps in categories. Here is a simple example of printing dealloc:

@implementation UIViewController (HMExtension)

+ (void)load {
    SEL s1 = NSSelectorFromString(@"dealloc");
    Method m1 = class_getInstanceMethod(self, s1);

    SEL s2 = NSSelectorFromString(@"hm_dealloc");
    Method m2 = class_getInstanceMethod(self, s2);
    if (class_addMethod(self, s1, method_getImplementation(m2), method_getTypeEncoding(m2))) {
        class_replaceMethod(self, s2, method_getImplementation(m1), method_getTypeEncoding(m1));
    } else{ method_exchangeImplementations(m1, m2); }} - (void)hm_dealloc {
    NSLog(@"%@ dealloc"[self class]);
    [self hm_dealloc];
}

@end
Copy the code

Both methods have their pros and cons, so I won’t analyze them here, because what follows is the focus of this article, a more non-intrusive approach than Method Swizzling, with zero lines of code to implement the print life cycle.

Print the life cycle using LLDB symbol breakpoints

Xcode allows us to add Symbolic Breakpoint breakpoints that the LLDB can trigger by setting symbols.

The LLDB can trigger a breakpoint to output context information by configuring symbols for the UIViewController lifecycle. The next in order toviewDidAppear:Methods for example, talk about the idea.

  1. First, as indicated above, the symbol line is configured like this-[UIViewController viewDidAppear:]. Run the project, and soon the program will trigger the breakpoint and stop and go into LLDB debug mode.
  2. How do you output the information you need? You can add one to the Action lineDebugger CommandCan be used to run LLDB commands. As we all know, the first two parameters of the OC message sending mechanism areselfand_cmdThese two keywords cannot be used directly in an LLDB environmentselfand_cmd. The most reliable is to value by register, in the method internal breakpoint, the first two parameters of the 64-bit emulator (x86_64) register is rDI and RSI, the first two parameters of the 64-bit true machine (ARM64) register is x0 and X1, preceded by$The symbol can be evaluated in the LLDB, assuming it is in the simulator, then type this command on this lineexpression -l objc -O -- @import UIKit; [[NSString alloc] initWithFormat:@"%@ %s", (id)$rdi, (char *)$rsi]. There is another approach, which the LLDB provides$arg1.$arg2.$arg3. In order to be compatible with both the simulator and the real machine, this line should be changedexpression -l objc -O -- @import UIKit; [[NSString alloc] initWithFormat:@"%@ %s", (id)$arg1, (char *)$arg2].
  3. Automatically continue after Evaluating Actions. Automatically continue after evaluating Actions. Automatically continue after evaluating Actions. The final result is as follows:

The dealloc method is also configured with symbolic breakpoints, run the Swift open source project Kingfisher demo, Xcode Console output GIF:

I changed the color of the Debugger output in Xcode Settings (orange) to distinguish it from the output of the APP execution (black).

Optimization: Reduce @import UIKit statements

The expression command executes two statements, starting with @import UIKit and then [[NSString alloc] initWithFormat:@”%@ %s”, (ID)$arg1, (char *)$arg2]. Maybe some apps don’t need to import UIKit framework, but I’ll keep it for stability. LLDB only needs to import UIKit once, and the above breakpoint is triggered once @import UIKit; . To reduce unnecessary imports, you can import UIKit once before triggering the UIViewController life cycle breakpoint. Set a symbolic breakpoint UIApplicationMain and add the Debugger Command Action, expression -l objc -o — @import UIKit, Then check Automatically continue after Evaluating Actions so that UIKit will be imported after your APP starts. As shown below:

The next step is to add a symbolic breakpoint to the UIViewController lifecycle method as we did in the previous section, Expression -l objc -o — [[NSString alloc] initWithFormat:@”%@ %s”, (id)$arg1, (char *)$arg2]

Optimization: Ignore the system-generated UIViewController

If you take a closer look at the GIF, you’ll see a UIInputWindowController appearing, which is a system-generated UIViewController. IOS will generate some UIViewControllers in some cases, and most of the time we don’t need to pay attention to the UIViewControllers generated by these systems, so we need to ignore these classes in order to reduce interference. Taking a closer look at the breakpoint editing window above, the Condition line allows class filtering. But if you do that, every time you add a class that needs to be ignored, there’s a lot to adjust: every project, every lifecycle symbol breakpoint in the project needs to be changed. In order to simplify the subsequent maintenance work, I wrote an LLDB script command plifecycle, which is collected in my open source command library HMLLDB. Readers can refer to the documentation to install this open source library. After a successful installation, the Debugger Command line can use plifecycle-i to print the lifecycle and ignore some of the UIViewControllers in the system without first @import UIKit. The configuration is shown as follows:

LLDB print life cycle benefits

I think LLDB is the most elegant way to print the UIViewController life cycle because it has the following advantages over the traditional method:

  • Zero lines of code, no intrusion, no need to modify the project, Git can not see any related record.
  • Xcode can set a unique color for the Debugger Output, and the Console window can be set to show only the Debugger Output, which is very easy to locate.
  • While the program is running, you can turn symbol breakpoints on or off as needed in Breakpoing Navigator to control whether to print at any time.

Existing problems

  • When you start the APP, you may trigger the following warning to suspend the application. You need to click “Continue Program Execution” in Xcode to Continue the application. An occasional problem may not appear the next time you reboot your computer.
# If this Warning is encountered, Xcode will Continue program Execution by clicking "Continue Program Execution" : hit breakpoint while running function, skipping commands and conditions to prevent recursion.Copy the code
  • Switching pages is stuck. Hitting a breakpoint to go into debug mode is costly in itself, plus the command to execute requires just-in-time compilation JIT, so page switching can be a bit slow. In general, it is not recommended to print all lifecycle methods, and it is recommended to only enable viewDidAppear: and dealloc printing for daily development.

Afterword.

As you may have noticed, both expression and plifecycle support printing any OC method, but in this article only the lifecycle. However, I do not rule out that the implementation method of Plifecycle will be changed in the future. After all, the problems mentioned above will affect the user experience.

The relevant data

HMLLDB:github.com/chenhuimao/… Plifecycle source: github.com/chenhuimao/… Kingfisher:github.com/onevcat/Kin…