iOs Debug Master

    Однажды я почти полностью отказался от мышки для навигации по Xcode и вполне этому рад. Следующий шаг — это отказ от визуальных средств управления отладчиком. Зачем? — Увеличиваем возможности, уменьшаем время дебага, тратим меньше калорий для перемещения тяжеленькой ручишки (нам калории нужны, чтобы головой работать) и тем самым провоцируем меньше туннельного синдрома.


    .
    Особо новых открытий в статье не будет, просто подытожу, что вы и сами хорошо знали и как это можно использовать для кого-то по-новому.

    Не рассматриваю такие темы: как работать в Xcode, как вообще отлаживать приложение, в какой момент нужно это делать и зачем, что такое LLDB, что такое Step Into и Step Over и т.д.

    Сразу к примерам:

    Настраиваем консоль


    Отказываемся вот от этой панельки:



    Да и вот про эту скоро забудем



    Теперь наш новый лучший друг вот этот заманчивый белый холст, можно растянуть его даже на всю длину или вообще весь экран.



    Спасибо моему коллеге: он осуществил мою давнишнюю мечту — открывать консоль отладчика в новом окне. А то всегда напрягало, что только открыл и настроил вкладки как тебе надо, так сразу же при первом брейкпоинте всё запоролось. Чтобы этого не было идем Xcode -> Behaviours -> Edit behaviours... Далее нам нужна секция Running -> Pause.

    Выбираем Show tab named, пишем туда наше уникальное название вкладки, например Debug (замечу, что при повторных запусках вкладка не дублируется), в конце ставим active window — это, как ни странно, открывает новую вкладку (Cmd + T) для нашего отладчика. Ешё я скрыл ненужную правую панель — но это по желанию. Вообще кастомизация среды при разных условиях — это в разделе Behaviours

    image

    Всё, мы настроены, двигаем дальше.

    Управляем отладчиком


    Добавляем сразу в копилку полезных hotkey'ев кто не знал Cmd + Shift + C. Это быстрый переход в консоль.
    Сmd + Shift + Y — это скрыть/показать консоль.
    Наши новые команды:
    Step Intostep или коротко s
    Step Overnext или коротко n
    Step Outfinish
    Continuecontinue или c
    Отключить все брейкпоинты — breakpoint disable

    За полным списком команд lldb можно отправиться вот сюда
    Не буду перечислять все возможности прямых команд LLDB, но их список больше, чем позволяет сделать визуальная среда Xcode и каждый сам вправе решить использовать ему подобный подход или нет и какие команды нужны и интересны. Остановлюсь на том, что показал куда смотреть.

    Удобство выполнения команд еще в том, что есть привычный как в терминале переход к предыдущей/последующей команде через стрелки вверх-вниз.

    В результате наша работа с отладчиком выглядит вот так:



    Swift и obj-c! Что внутри объекта?


    Не знаю как вам, но мне не повезло писать проект на Swift с нуля, а пришлось с выходом нового языка в огромном obj-c проекте все новые файлы писать на Swifte. Не пишу про все несостыковки, но основная проблема в отладке, наверное, в следующем:

    Создаем простой класс модели на Swift

    class TestObject: NSObject {
        var name: String = "name"
        var index: Int = 123
    }
    

    Теперь пишем на obj-c простой массив с нашим классом:
    NSArray *array = @[[[TestObject alloc] init],
                     [[TestObject alloc] init],
                     [[TestObject alloc] init]];
    

    Теперь ставим брейкпоинт на следующей строчке и смотрим на то, что бы мы увидели в Variables:

    image

    Знакомо? А где же наши name и index
    Теперь смотрим, что мы можем сами руками в консоли:

    (lldb) p array
    (__NSArrayI *) $0 = 0x00007f9a1878c6b0 @"3 objects"
    (lldb) po array
    <__NSArrayI 0x7f9a1878c6b0>(
    <CornerApp.TestObject: 0x7f9a1870fc60>,
    <CornerApp.TestObject: 0x7f9a1878c650>,
    <CornerApp.TestObject: 0x7f9a1878c680>
    )
    
    (lldb) po [array debugDescription]
    <__NSArrayI 0x7f9a1878c6b0>(
    <CornerApp.TestObject: 0x7f9a1870fc60>,
    <CornerApp.TestObject: 0x7f9a1878c650>,
    <CornerApp.TestObject: 0x7f9a1878c680>
    )
    
    (lldb) po array[0]
    <CornerApp.TestObject: 0x7f9a1870fc60>
    
    (lldb) po array[0].name
    error: property 'name' not found on object of type 'id'
    error: 1 errors parsing expression
    (lldb) po [array[0] name]
    name
    
    (lldb) 
    

    p — это print
    po — это print object, кидает объекту сообщение description, обратите внимание, что объектам еще можно послать сообщение debugDescription
    po array[0].name — не работает, потому что для отладчика нулевой элемент в массиве типа id. А вот посылка сообщения name (po [array[0] name]) прекрасно работает. Не забываем, про то, что obj-c — это message oriented язык программирования.

    Найди меня


    Следующий кейс: у нас есть API — мы ходим на сервер за списком стран, потом преобразуем их во внутреннюю логику и где-то храним. Например, наша modelView идет за данными в хранилище и ищет модельку в словаре по ключу:
    - (CACountry *)countryByCode:(NSString *)code
    {
        return  [_countries objectForKey:code];
    }
    

    Что мы видим в Variables? 242 страны, отлично, как нам быстро найти Россию?
    image

    Знаю, вот так:
    (lldb) po [_countries objectForKey:@"RU"]
    <CACountry: 0x7fae00630ba0>
    
    (lldb) po [[[_countries objectForKey:@"RU"] title] string]
    Россия
    

    И хочу заметить, что в строке отладчика прекрасно работает автодополнение для посылки сообщений в objective-c

    Магические UIView


    Наверняка, если делали какой-нибудь нетривиальный UI были ситуации, когда что-нибудь в интерфейсе съехало и нужно разобраться, кто виноват. При это перезапуск приложение грозит тем, что может не удастся воспроизвести ситуацию. Вот тут-то работа с отладчиком незаменима.
    В результате выполнения простого
    (lldb) po self.view.subviews
    

    увидим что-то вроде такого:
    <UICollectionView: 0x7fd9fb81a200; frame = (0 0; 375 667); clipsToBounds = YES; opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fd9fb416cb0>; layer = <CALayer: 0x7fd9fb4067f0>; contentOffset: {0, -180}; contentSize: {375, 843}> collection view layout: <TGLStackedLayout: 0x7fd9fb410000>,
    <CASortView: 0x7fd9fb4468c0; frame = (67.5 607; 240 60); clipsToBounds = YES; alpha = 0; opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7fd9fb481290>>,
    <UIView: 0x7fd9fb47e240; frame = (0 0; 375 687); alpha = 0; layer = <CALayer: 0x7fd9fb4895a0>>
    

    Сразу видим набор view, их фреймы, свойства, всё то, что запихнули разработчики Apple в стандартное сообщение description.
    Если нужно легко можем уточнить какое-нибудь свойство, можно использовать адрес в памяти напрямую:
    (lldb) po [0x7fae2b792ba0 backgroundColor]
    UIDeviceWhiteColorSpace 0 1
    

    А вот вам напоследок кусок кода, который может доставать по иерархии все view заданного класса, может покажется полезным. Сам использую.
    import UIKit
    
    extension UIView {
        
        func debugAllSubviewsOfClass(cls: AnyClass) -> [String] {
            func goDeepAndPrint(inout views: [String], currentView: UIView) {
                for v in currentView.debugSubview() {
                    views.append(v.debugDescriptionWithParent())
                    goDeepAndPrint(&views, currentView:v)
                }
            }
            var views = [String]()
            goDeepAndPrint(&views, currentView:self)
            return views
        }
        
        func debugDescriptionWithParent() -> String {
            let parentAddress = self.superview != nil ? String(format: "%p", self.superview!) : "nil"
            return "\(self.description), parent = \(parentAddress)"
        }
        
        func debugSubview() -> [UIView] {
            return self.subviews
        }
    }
    
    extension UITableView {
        
        override func debugSubview() -> [UIView] {
            return self.subviews + self.visibleCells
        }
    }
    
    extension UICollectionView {
        
        override func debugSubview() -> [UIView] {
            return self.subviews + self.visibleCells()
        }
    }
    

    UPD: расширяем набор команд LLDB


    Есть неплохая коллекция забинденных команд для LLDB от Facebook — Chisel
    Например, там уже есть команда с рекурсивным выводом вложенных view — pviews и контроллеров — pvc Есть удобные команды show/hide, которые позволяют без continue скрыть или показать тот или иной слой, view.
    В гите есть описание команд и инструкции по установке, хочу лишь заметить, что вам нужен менеджер пакетов brew, если его нет и создать руками файл .lldbinit, которого у вас, как у меня, может не быть и добавить туда код, который выдает brew после установки chisel'a:
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    ...
    brew install chisel
    ==> Downloading https://github.com/facebook/chisel/archive/1.3.0.tar.gz
    ==> Downloading from https://codeload.github.com/facebook/chisel/tar.gz/1.3.0
    ######################################################################## 100.0%
    ==> Caveats
    Add the following line to ~/.lldbinit to load chisel when Xcode launches:
      command script import /usr/local/opt/chisel/libexec/fblldb.py
    

    Затем создаем файл .lldbinit в корневой директории и запихиваем туда скрипт:
    command script import /usr/local/opt/chisel/libexec/fblldb.py
    

    Обратите внимание, что скрипт может меняться, например, тот, что указан в гите самого chisel отличается.
    Команды будут доступны при перезапуске Xcode, если лень это делать, можно:
    command source ~/.lldbinit
    

    Summary


    • С отладчиком можно работать не только через кнопочки Xcode.
    • Как настроить открытие консоли при отладке в новой вкладке? Самое начало — большая картинка
    • Быстрый переход в консоль — Cmd + Shift + C,
      свернуть/развернуть консоль — Cmd + Shift + Y
    • Наши новые команды:
      Step Intostep или коротко s
      Step Overnext или коротко n
      Continuecontinue или c
      Отключить все брейкпоинты — breakpoint disable
      Вот тут весь список команд LLDB.
    • Obj-c не видит свойства объекта Swift-ового класса? Не беда:
      (lldb) po [array[0] name]
      
    • В окне variables 242 ключа у словаря?
      Ищем быстро:
      (lldb) po [[[_countries objectForKey:@"RU"] title] string]
      Россия
      
    • Ставим chisel и расширяем набор команд LLDB


    Всем спасибо, кто прочитал. Надеюсь, пару приёмчиков кто-то для себя новых открыл.

    Описал каждую из возможностей не подробно, но старался показать направление, кому нужно — может разобраться глубже или спросить в комментариях!
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 13
    • +1
      Первый и самый важный приемчик, который я в этой статье для себя открыл — это как можно в XCode посмотреть возвращаемые значения для сообщений, да и вообще произвольную информацию в текущем контексте. После IDEA очень не хватало такой возможности и думал, что в XCode её нет. Спасибо!
      • НЛО прилетело и опубликовало эту надпись здесь
        • +2
          у AppCode своих проблем вагон.
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Лучше сразу TDD, чтоб отлаживать нечего было ;)
        • 0
          > Это быстрый переход в консоль.
          Напишите, пожалуйста, название этой и других команд в Preferences / Key Bindings.
          • 0
            View menu -> Debug Area -> Activate Console Cmd + Shift + C
            View menu -> Debug Area -> Show Debug Area Cmd + Shift + Y
          • +1
            >А вот вам напоследок кусок кода, который может доставать по иерархии все view заданного класса, может покажется полезным.
            Попробуйте recursiveDescription
          • 0
            ох, я надеялся что вы вытяните самый сок из курса Ray Wenderlich по advance использованию LLDB

            Video Tutorial: Using LLDB in iOS Part 1
            www.raywenderlich.com/71588/video-tutorial-using-lldb-ios-part-1-getting-started
            там только первое видео бесплатное, но я очень рекомендую с ним ознакомится. Ибо как они там говорили: «нельзя быть самым крутым на вечеринке, когда не знаешь как делать программируемые breakpoints (которые срабатывают лишь при определённых условиях или значениях)
            • 0
              Вопрос по теме: последнее время постоянно nil в отладчике, почти ничего не показывает, как будто все свойства и переменные пустые. Как быть? Попробовал через консоль, пишет: «error: Couldn't materialize: couldn't get the value of variable receipt: no location, value may have been optimized out
              Errored out in Execute, couldn't PrepareToExecuteJITExpression».
              • 0
                Судя по всему у вас стоит оптимизация, или вы strip debug symbols. В общем, посмотрите внимательно, например, такой линк. Тут, вероятно, ваша проблема и её подробное решение
                • 0
                  Действительно, по недосмотру стояла схема релизная. Как-то и не заметил. Гуглил — всё не то было… Спасибо.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.