recursiveDescription method in Swift?
Asked Answered
S

8

40

Is there a way to use [self.view recursiveDescription] in Swift? I am trying to use this method but I am getting the following error:

'UIView' does not have a member named 'recursiveDescription'

Any suggestions?

Stoat answered 17/9, 2014 at 21:25 Comment(1)
FYI, If you want to use [self.view recursiveDescription] while debugging Swift or Obj-C, you can pause the app and po [self.view recursiveDescription]. [[UIWindow keyWindow] _autolayoutTrace] is great for debugging view constraints especially used in conjunction with XCode6's visual debugger.Owen
D
11

In order to access private / undocumented Objective-C API (like the -recursiveDescription method on UIView) from Swift you can do the following:

  1. Create a new Objective-C category on the class the private method is defined in (e.g. UIView).
  2. Hit Yes if Xcode asks you about configuring an bridging header. (If you have already an bridging header in your project this step will be skipped).
  3. The implementation file (.m) of the category can be removed.
  4. Declare the private method in the category header:

    // UIView+Debugging.h
    
    @interface UIView (Debugging)
    - (id)recursiveDescription;
    @end
    

Now you can set a breakpoint and print out the recursive description in LLDB:

po view.recursiveDescription() as NSString
Dropping answered 29/12, 2014 at 19:3 Comment(2)
this technique works great but swift/lldb are messing it up. i have no clue why, but the output i get (XCode 6.2 6C101) from this technique is horribly messed up, includes \n as distinct characters so actual line-breaks in the console - which were awesome - and it clips at about 1800 chars, making it pretty worthless. ugh.Concertgoer
Just wonder will Apple reject this if I used this in product code?Queenstown
P
90

If you want to display the view hierarchy in lldb, you do not have to add any categories or bridging headers or anything like that. When debugging Objective-C code, one would generally use the following command at the (lldb) prompt:

po [[UIWindow keyWindow] recursiveDescription]

If, though, you've paused in a Swift frame, lldb may expect a Swift expression. You can, though, explicitly tell expr (the po abbreviation is actually calling expression) which language the expression is in:

expr -l objc++ -O -- [[UIWindow keyWindow] recursiveDescription]

The same patterns occur in iOS 8, when viewing the view controller hierarchy, using:

po [UIViewController _printHierarchy]

or, in Swift frame:

expr -l objc++ -O -- [UIViewController _printHierarchy]

In WWDC 2018 Advanced Debugging with Xcode, they suggest getting yourself away from this complicated expr syntax by defining an alias, poc, by creating a text file, ~/.lldbinit with the following line:

command alias poc expression -l objc -O --

Then you can do things like:

poc [UIViewController _printHierarchy]

It's worth noting that Xcode 8 introduced the view debugger (click on view debug button), offering a more interactive way to analyze the view hierarchy, largely eliminating the need for the LLDB recursiveDescription of the view hierarchy. (This was discussed in WWDC 2016 video Visual Debugging with Xcode (which is no longer available). Admittedly, sometimes we end up having to fall back to the recursiveDescription technique shown above, but most of the time the view debugger makes this a far more natural, intuitive process.

And in Xcode 9, they've expanded this view debugger so it now includes the relevant view controllers, too:

enter image description here

Paraphernalia answered 20/2, 2015 at 15:23 Comment(6)
Ron's answer is simpler for Swift 2.0, but this is useful lldb knowledgeClair
Good info! Any ideas why self isn't available when changing the language to objc? E.g. if I breakpoint inside an instance method, "expr -l swift -- print(self)" successfully prints a description of self, while "expr -l objc -- NSLog(@"%@", self)" results in "use of undeclared identifier 'self'"Python
Note, to test yourself you'll need to first: expr -l objc -- @import UIKitPython
@LouZ. - First, when doing this, you don't need NSLog or print. The expr will already print out the expression for you. Second, and in answer to your question, no I don't know why precisely you cannot refer to self when using Objective-C while in Swift frame (or vice versa, BTW), but it doesn't seem that surprising, either. Obviously, when using [[UIWindow keyWindow] recursiveDescription] or [UIViewController _printHierarchy], you don't have to refer to self at all, so it's a non issue, but it's an interesting observation.Paraphernalia
It's also sometimes easier to print the view object in Swift if the hierarchy is complex (like po self.foo.bar.someView) to get its address. You get an output like <UIView: 0x10bd0a600; …>. Using that address, you can then dump the hierarchy using Rob's trick like this: expr -l objc++ -O -- [0x10bd0a600 recursiveDescription]Foothold
largely eliminating the need for the LLDB -- One key case I've found is debugging CALayers; UI debugger doesn't seem to render them and I've had to resort to recursiveDescription.Plumbism
I
70

In swift 2.0 you can simply run:

po view.performSelector("recursiveDescription")

In (tested with iOS10 Beta3) swift 3.0 this is a bit more complex:

po let s = view.perform("recursiveDescription"); print(s)

Investiture answered 30/9, 2015 at 15:56 Comment(1)
This should be the accepted answer because it requires no additional code.Buzzer
O
33
po view.value(forKey: "recursiveDescription")!
Odaniel answered 5/12, 2016 at 16:13 Comment(1)
This is the most simple solution as it works with Swift 5 and requires no additional code nor complex lldb hackingBurp
D
11

In order to access private / undocumented Objective-C API (like the -recursiveDescription method on UIView) from Swift you can do the following:

  1. Create a new Objective-C category on the class the private method is defined in (e.g. UIView).
  2. Hit Yes if Xcode asks you about configuring an bridging header. (If you have already an bridging header in your project this step will be skipped).
  3. The implementation file (.m) of the category can be removed.
  4. Declare the private method in the category header:

    // UIView+Debugging.h
    
    @interface UIView (Debugging)
    - (id)recursiveDescription;
    @end
    

Now you can set a breakpoint and print out the recursive description in LLDB:

po view.recursiveDescription() as NSString
Dropping answered 29/12, 2014 at 19:3 Comment(2)
this technique works great but swift/lldb are messing it up. i have no clue why, but the output i get (XCode 6.2 6C101) from this technique is horribly messed up, includes \n as distinct characters so actual line-breaks in the console - which were awesome - and it clips at about 1800 chars, making it pretty worthless. ugh.Concertgoer
Just wonder will Apple reject this if I used this in product code?Queenstown
S
10

First add a category @interface without @implementation in your bridging header.

@interface UIView (Debug)
- (id)recursiveDescription;
- (id)_autolayoutTrace;  // This one is even sweeter
@end

then in your console

po self.recursiveDescription() as NSString
po self._autolayoutTrace() as NSString

The key here is as NSString not as String

Shocking answered 21/1, 2015 at 8:51 Comment(2)
How did you know to cast as NSString?Pointer
Plus one for _autolayoutTrace. It is pretty sweet!Person
A
4
expression -l objc -O -- [`self.view` recursiveDescription]

There is needed enter it in Objective-C format because UIKit is in Objective-C framework.

Recursive description only exists for debugging purposes. It's not part of the public API and so isn't available to Swift

And Swift is a strict language and doesn't allow you to call functions that haven't been strictly defined.

Objective-C, it's a dynamic language so you can call functions like this.

So what we need to do is to tell the debugger to evaluate this expression in an Objective-C syntax

And the way to do that is to use expression with the option - l objc

-O, tell the debugger that we also want the debug description the same as po would do and -- to indicate that there are no more options.

We need to put put [self.view] view in back ticks.

[`self.view`]

Back ticks is like a preproccess step that says first, evaluate the contents of this in the current frame and insert the result, and then we can evaluate the rest.

My answer is taken from WWDC 2018 Session 412 advanced debugging with Xcode and lldb.

Aut answered 2/9, 2018 at 22:37 Comment(2)
While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value.Supersaturate
Hello Nic3500, I extended my previous answer.Aut
D
2

Add to the bridging header a declaration of a category of UIView with that method.

Darees answered 18/9, 2014 at 1:39 Comment(1)
Just in case another novice like me need more details :) petosoft.wordpress.com/2014/06/13/lldb-and-xcode-6Piscator
F
1

Building on @Rob Mayoff's answer:

extension UIView {

   /**
    Wrapper for useful debugging description of view hierarchy
   */
   var recursiveDescription: NSString {
       return value(forKey: "recursiveDescription") as! NSString
   }

}
Flower answered 4/9, 2018 at 2:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.