Как использовать _printHierarchy в консоли LLDB с Swift?

Apple добавила закрытый помощник _printHierarchy в iOS8, который можно использовать в консоли LLDB:

po [[[UIWindow keyWindow] rootViewController] _printHierarchy]

который печатает всю иерархию контроллера представления в текстовой форме.

Это работает, только если вы отлаживаете код на Objective C. В Swift это не работает:

(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]
                                           ^
                                          ,

Эквивалентное использование в Swift также не работает:

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

заканчивается ошибкой (возможно, потому что _printHierarchy является частным свойством):

(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
                                                               ^ ~~~~~~~~~~~~~~~

Вопрос: Как распечатать иерархию диспетчера представлений в Swift? Или есть способ использования ObjC в консоли LLDB даже в проектах Swift?

Ответы

Ответ 1

Вы указываете, как отображается иерархия контроллера представления с помощью:

po [[[UIWindow keyWindow] rootViewController] _printHierarchy]

Затем вы говорите:

Это работает, только если вы отлаживаете код на Objective C. В Swift это не работает.

Собственно, это немного зависит от того, как вы приостанавливаете выполнение своей программы Swift. Проблема в том, что команда expression (в которой используется po) будет использовать выражения Swift в кадрах Swift и выражения Objective-C в кадрах Objective-C. Таким образом, это означает, что поведение po варьируется в зависимости от того, как выполнение приостанавливается:

  • Вы можете, например, нажать кнопку "пауза" во время работы приложения:

    pause

    Если вы сделаете это, вы сможете использовать приведенный выше синтаксис po с выражением Objective-C без инцидентов.

  • Если, с другой стороны, вы устанавливаете точку останова внутри вашего кода Swift, вы попадете в рамку Swift, когда вы перейдете к приглашению (lldb). Но вы можете явно сообщить команде expression, что вы хотите использовать синтаксис Objective-C с опцией -l (или --language):

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

Эта возможность указать язык в команде expr обсуждается в видео WWDC 2014 Расширенная Swift-отладка в LLDB.

Ответ 2

Если вы остановились в коде Swift, вставьте эту строку в консоль отладчика (после подсказки (lldb)) и нажмите клавишу ввода, чтобы распечатать иерархию контроллера корневого представления:

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

Если вы остановились в коде Objective-C кода или кода сборки, используйте эту строку вместо:

po [UIWindow valueForKeyPath:@"keyWindow.rootViewController._printHierarchy"]

Ответ 3

Похоже, поскольку это помощник "private", он каким-то образом не подвергается воздействию Swift. Он также недоступен из Objective-C, т.е.

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

приводит к ошибке времени компиляции. Однако то, что может работать, заключается в использовании NSSelectorFromString внутри заголовка моста, например.

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

Как только это определено, вы можете вызвать printHierarchyWithVC из Swift.

Ответ 4

После некоторых исследований я узнал, что это просто вопрос об экспорте этого конкретного API (скопированный из заголовки времени выполнения iOS) в заголовке проекта поэтому он становится доступным для Swift:

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

Во время выполнения этот метод класса можно вызвать следующим образом:

(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

... распечатать иерархию контроллера представления. Обратите внимание, что метод должен вызываться только в основном потоке (UI).

Ответ 5

Я рекомендую использовать expr -l objc++ -O -- [UIViewController _printHierarchy] в консоли, так как он распечатает всю иерархию представлений в текстовой форме, которую я нашел значительно более полезной, чем UIWindow.valueForKeyPath... Обратите внимание: вам не нужно добавлять po в напечатайте иерархию, просто используйте как есть.

Это работает для меня, Xcode 8/swift 3, хотя я думаю, что одна и та же команда также работает и в более ранних версиях Xcode, потому что она похожа на объективную C.

Пример вывода из этой команды:

(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>

Ответ 6

После установки Chisel вы можете просто сделать следующее и более:

(lldb) pvc

Ответ 7

Параметры, которые уже были опубликованы здесь, отличные, еще один вариант (похожий на этот ответ), если вам абсолютно необходимо использовать SWIFL-контекст lldb (что означает вы не хотите передавать -l objc, вы можете позвонить performSelector:

который соединяется с Swift следующим образом:

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

В этом случае вы бы назвали это следующим образом:

po UIApplication.shared.keyWindow!.rootViewController!.perform("_printHierarchy")!.takeUnretainedValue()