26

Apple added a private helper _printHierarchy in iOS8 that can be used in LLDB console:

po [[[UIWindow keyWindow] rootViewController] _printHierarchy]

which prints out the whole view controller hierarchy in text form.

This works only if you are debugging code on Objective C. In Swift, however, this doesn't work:

(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
error: <EXPR>:1:13: error: expected ',' separator
[[[UIWindow keyWindow] rootViewController] _printHierarchy]
            ^
           ,
<EXPR>:1:24: error: expected ',' separator
[[[UIWindow keyWindow] rootViewController] _printHierarchy]
                       ^
                      ,
<EXPR>:1:44: error: expected ',' separator
[[[UIWindow keyWindow] rootViewController] _printHierarchy]
                                           ^
                                          ,

An equivalent usage in Swift doesn't work either:

po UIApplication.sharedApplication().keyWindow!.rootViewController!._printHierarchy

ends up with an error (probably because _printHierarchy is a private property):

(lldb) po UIApplication.sharedApplication().keyWindow!.rootViewController!._printHierarchy()
error: <EXPR>:1:64: error: 'UIViewController' does not have a member named '_printHierarchy'
UIApplication.sharedApplication().keyWindow!.rootViewController!._printHierarchy
                                                               ^ ~~~~~~~~~~~~~~~

The question is: How to print out the view controller hierarchy in Swift? Or is there a way how to use ObjC in LLDB console even in Swift projects?

Tom Kraina
  • 3,569
  • 1
  • 38
  • 58

7 Answers7

48

You point out how one shows the view controller hierarchy with:

po [[[UIWindow keyWindow] rootViewController] _printHierarchy]

You then say:

This works only if you are debugging code on Objective C. In Swift, however, this doesn't work.

Actually, this depends a little upon how you pause the execution of your Swift program. The issue is that the expression command (which po uses) will use Swift expressions in Swift frames, and Objective-C expressions in Objective-C frames. Thus this means that the po behavior varies depending upon how the execution pauses:

  • You can, for example, press the "pause" button while the app is running:

    pause

    If you do this, you will be able to use the above po syntax with the Objective-C expression without incident.

  • If, on the other hand, you set a breakpoint inside your Swift code, you'll be in a Swift frame when you get to the (lldb) prompt. But you can explicitly tell the expression command that you want to use the Objective-C syntax with the -l (or --language) option:

    expr -l objc++ -O -- [[[UIWindow keyWindow] rootViewController] _printHierarchy]
    

This ability to specify the language in the expr command is discussed in WWDC 2014 video Advanced Swift Debugging in LLDB.

slxl
  • 635
  • 1
  • 10
  • 23
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hmm, I've tried that and I updated my question with an error I get in console. – Tom Kraina Feb 19 '15 at 15:26
  • @TomKraina If you use a breakpoint in Swift code, you're in a Swift frame and thus `po` expects Swift expression. If, however, you just hit pause button while the Swift program is running (which is what I'm often doing), you will more than likely not be in a Swift frame when you get the `(lldb)` prompt, and thus the Objective-C expression is fine. Fortunately, if you pause the app in Swift frame, you can still explicitly provide `--language` option (or `-l` option) to specify that even if you're in Swift frame, that it should interpret Objective-C expression. See revised answer. – Rob Feb 19 '15 at 19:00
  • 1
    That's cool! I didn't know you can specify a language in `lldb`! – Tom Kraina Feb 20 '15 at 16:31
  • or just `expr -l objc++ -O -- [UIViewController _printHierarchy]` – Logachu Nov 02 '17 at 22:00
13

If you are stopped in Swift code, paste this line into the debugger console (after the (lldb) prompt) and press enter to print the hierarchy of the root view controller:

po UIWindow.value(forKeyPath: "keyWindow.rootViewController._printHierarchy")!

If you are stopped in Objective-C code or assembly code, use this line instead:

po [UIWindow valueForKeyPath:@"keyWindow.rootViewController._printHierarchy"]
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
2

The options that have already been posted here are great, another option (similar to this answer), if you absolutely need to be using the Swift lldb context (meaning you don't want to pass -l objc, is you can call performSelector:

Which is bridged to Swift like this:

func perform(_ aSelector: Selector!) -> Unmanaged<AnyObject>!

For this case you would call it like this:

po UIApplication.shared.keyWindow!.rootViewController!.perform("_printHierarchy")!.takeUnretainedValue()
Keith Smiley
  • 61,481
  • 12
  • 97
  • 110
1

It seems like, since this is a "private" helper, it is somehow not exposed to Swift. It is also not accessible from within Objective-C, i.e.

UIViewController* vc = // Assign view controller
[vc _printHierarchy];

results in a compile time error. However, what might work is using NSSelectorFromString inside a bridging header, e.g.

-(void) printHierarchyWithVC:(UIViewController*) vc
{
    [vc performSelector: NSSelectorFromString(@"_printHierarchy")];
}

Once this is defined, you can call printHierarchyWithVC from Swift.

Sebastian
  • 8,046
  • 2
  • 34
  • 58
1

After some research, I found out that it's just a matter of exposing this particular API (copied from iOS runtime headers) in project's bridging header so it becomes available to Swift:

@interface UIViewController (Debugging)
+ (id)_printHierarchy;
@end

During runtime, this class method can be called as follows:

(lldb) po UIViewController._printHierarchy() as NSString
<UINavigationController 0x7f8a50733c70>, state: appearing, view: <UILayoutContainerView 0x7f8a5064def0>
   | <MyApp.RootViewController 0x7f8a507341f0>, state: appearing, view: <UIView 0x7f8a5056d860> not in the window

...printing out the view controller hierarchy. Note that the method must be called only on the main (UI) thread.

Tom Kraina
  • 3,569
  • 1
  • 38
  • 58
1

I recommend using expr -l objc++ -O -- [UIViewController _printHierarchy] in the console as it will print out the full view hierarchy in text form, which I found significantly more useful than UIWindow.valueForKeyPath... Note that you do not need to add po to print the hierarchy, just use as is.

This works for me, Xcode 8 / swift 3 though I think the same command also works in earlier versions of Xcode too because it looks like it is objective C.

example output from this command:

(lldb) expr -l objc++ -O -- [UIViewController _printHierarchy]

<MyProject.SwipeController 0x102213590>, state: disappeared, view: <UIView 0x102239ff0> not in the window
   + <MyProject.CameraViewController 0x102215680>, state: disappeared, view: <UIView 0x102422fd0> not in the window, presented with: <_UIFullscreenPresentationController 0x102219c60>
   |    + <MyProject.MapViewController 0x102214820>, state: appeared, view: <UIView 0x10bf52fe0>, presented with: <_UIFullscreenPresentationController 0x10fd1f890>
   |    |    | <MyProject.MapPlaceCollectionViewController 0x10bf54680>, state: appeared, view: <UICollectionViewControllerWrapperView 0x1022438d0>
Natalia
  • 1,308
  • 16
  • 23
1

After installing Chisel you can simply do the following and more:

(lldb) pvc

Tom Kraina
  • 3,569
  • 1
  • 38
  • 58