Мобильная разработка
0,0
рейтинг
17 ноября 2015 в 08:39

Разработка → 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


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

Описал каждую из возможностей не подробно, но старался показать направление, кому нужно — может разобраться глубже или спросить в комментариях!
Ртищев Евгений @katleta
карма
16,0
рейтинг 0,0
Мобильная разработка
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (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
      Спасибо, посмотрю
  • 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
        Действительно, по недосмотру стояла схема релизная. Как-то и не заметил. Гуглил — всё не то было… Спасибо.

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