Swift中的recursiveDescription方法是什么?

40

有没有办法在Swift中使用[self.view recursiveDescription]?我正在尝试使用这个方法,但是我遇到以下错误:

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

有什么建议吗?


2
如果你想在调试Swift或Obj-C时使用[self.view recursiveDescription],你可以暂停应用并输入po [self.view recursiveDescription]。[[UIWindow keyWindow] _autolayoutTrace]非常适合调试视图约束,特别是与XCode6的可视化调试器一起使用。 - Matthew Korporaal
8个回答

90
如果您想在lldb中显示视图层次结构,则无需添加任何类别、桥接标头或其他内容。在调试Objective-C代码时,通常会在(lldb)提示下使用以下命令:
po [[UIWindow keyWindow] recursiveDescription]

如果你在Swift框架中暂停了,lldb可能期望一个Swift表达式。不过你可以显式地告诉`expr`(`po`缩写实际上是调用`expression`),表达式使用的是哪种语言:
expr -l objc++ -O -- [[UIWindow keyWindow] recursiveDescription]

在iOS 8中查看视图控制器层次结构时,会出现相同的模式,使用以下方法:
po [UIViewController _printHierarchy]

或者,在 Swift 框架中:
expr -l objc++ -O -- [UIViewController _printHierarchy]

在WWDC 2018 使用Xcode进行高级调试中,他们建议通过定义别名poc来远离这个复杂的expr语法,方法是创建一个名为~/.lldbinit的文本文件,并添加以下行:
command alias poc expression -l objc -O --

然后你可以做像这样的事情:
poc [UIViewController _printHierarchy]

值得注意的是,Xcode 8引入了视图调试器(点击view debug button),提供了一种更交互式的分析视图层次结构的方式,大大减少了对视图层次结构的LLDB recursiveDescription的需求。 (这在WWDC 2016视频使用Xcode进行可视化调试中讨论过(不再可用)。诚然,有时我们会不得不回到上面展示的recursiveDescription技术,但大多数情况下,视图调试器使这个过程更加自然,直观。

而在Xcode 9中,他们扩展了这个视图调试器,现在也包括相关的视图控制器:

enter image description here


2
Ron的答案对于Swift 2.0来说更简单,但这是lldb知识的有用补充。 - Jacob Jennings
好的信息!有什么想法为什么在将语言更改为objc时self不可用?例如,如果我在实例方法内设置断点,“expr -l swift -- print(self)”成功打印出self的描述,而“expr -l objc -- NSLog(@”%@“,self)”则会导致“使用未声明的标识符'self'”。 - Lou Zell
注意,要测试自己,您需要首先执行以下操作:expr -l objc -- @import UIKit - Lou Zell
1
@LouZ。- 首先,当执行此操作时,您不需要使用NSLogprintexpr将为您打印表达式。 其次,并回答您的问题,我不知道为什么在使用Objective-C时无法引用self,而在Swift框架中(反之亦然),但这似乎也不奇怪。 显然,在使用[[UIWindow keyWindow] recursiveDescription][UIViewController _printHierarchy]时,您根本不需要引用self,因此这不是问题,但这是一个有趣的观察。 - Rob
如果视图层次结构很复杂(例如 po self.foo.bar.someView),在Swift中打印视图对象有时更容易,以获取其地址。您将获得类似于 <UIView: 0x10bd0a600; …> 的输出。使用该地址,您可以使用Rob的技巧转储层次结构,如下所示:expr -l objc++ -O -- [0x10bd0a600 recursiveDescription] - DarkDust
“largely eliminating the need for the LLDB” -- 我发现一个关键的情况是调试CALayer; UI调试器似乎无法渲染它们,我不得不使用recursiveDescription。 - Graystripe

68
在 Swift 2.0 中,您只需运行以下命令:
po view.performSelector("recursiveDescription")

在Swift 3.0中(已使用iOS10 Beta3进行测试),这个过程会变得更为复杂: po let s = view.perform("recursiveDescription"); print(s)

6
这应该是被接受的答案,因为它不需要额外的代码。 - user3246173

33
po view.value(forKey: "recursiveDescription")!

1
这是最简单的解决方案,因为它适用于Swift 5,并且不需要任何额外的代码或复杂的lldb黑客技巧。 - mbi

11
为了访问私有/未公开的Objective-C API(例如在UIView上的-recursiveDescription方法),从Swift中可以执行以下操作:
  1. 在定义了私有方法的类(例如UIView)上创建一个新的Objective-C分类。
  2. 如果Xcode询问您是否要配置桥接头文件,请选择"Yes"。(如果您的项目中已经有桥接头文件,则会跳过此步骤)。
  3. 可以删除该分类的实现文件(.m)。
  4. 在分类头文件中声明私有方法:

    // UIView+Debugging.h
    
    @interface UIView (Debugging)
    - (id)recursiveDescription;
    @end
    
    现在您可以在LLDB中设置断点并打印递归描述:

现在您可以在 LLDB 中设置断点并打印递归描述:

po view.recursiveDescription() as NSString

3
这种技术很好用,但是Swift/LLDB搞砸了它。我不知道为什么,但我从这种技术中得到的输出(XCode 6.2 6C101)非常混乱,包括\n作为不同的字符,因此在控制台上实际的换行符 - 这些本来很棒 - 并且它剪切到大约1800个字符左右,使其几乎没有价值。唉。 - natbro
我想知道如果我在产品代码中使用这个,苹果会不会拒绝它? - Itachi

10

首先在你的桥接头文件中添加一个没有@implementation 的类别 @interface

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

在您的控制台中执行以下命令:

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

关键在于使用as NSString 而非 as String

你是怎么知道要转换成NSString的? - MattD
为 _autolayoutTrace 加一分。它确实非常棒! - Clay Ellis

4
expression -l objc -O -- [`self.view` recursiveDescription]

由于UIKit是Objective-C框架,因此需要以Objective-C格式输入。
递归描述仅用于调试目的。它不是公共API的一部分,因此在Swift中不可用。
Swift是一种严格的语言,不允许您调用未经严格定义的函数。
Objective-C是一种动态语言,因此您可以像这样调用函数。
因此,我们需要告诉调试器以Objective-C语法评估此表达式。
而要做到这一点,就需要使用带有选项-l objc的表达式。
-O表示我们还想要与po相同的调试描述,并使用--指示没有更多选项。
我们需要将[self.view]视图放入反引号中。
[`self.view`]

反引号就像一个预处理步骤,它首先在当前框架中评估其内容并插入结果,然后我们可以评估其余部分。

我的答案来自于WWDC 2018 Session 412高级调试与Xcode和lldb。


虽然这段代码可能回答了问题,但提供有关它如何以及/或为什么解决问题的附加上下文将改善答案的长期价值。 - Nic3500
你好Nic3500,我扩展了我的先前回答。 - ManuelMB

2

在桥接头文件中添加一个UIView类别的声明,该类别具有该方法。


以防像我这样的新手需要更多细节 :) http://petosoft.wordpress.com/2014/06/13/lldb-and-xcode-6/ - iamdavidlam

1

@Rob Mayoff答案基础上进行扩展:

extension UIView {

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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接