.
Особо новых открытий в статье не будет, просто подытожу, что вы и сами хорошо знали и как это можно использовать для кого-то по-новому.
Не рассматриваю такие темы: как работать в Xcode, как вообще отлаживать приложение, в какой момент нужно это делать и зачем, что такое LLDB, что такое Step Into и Step Over и т.д.
Сразу к примерам:
Настраиваем консоль
Отказываемся вот от этой панельки:
Да и вот про эту скоро забудем
Теперь наш новый лучший друг вот этот заманчивый белый холст, можно растянуть его даже на всю длину или вообще весь экран.
Спасибо моему коллеге: он осуществил мою давнишнюю мечту — открывать консоль отладчика в новом окне. А то всегда напрягало, что только открыл и настроил вкладки как тебе надо, так сразу же при первом брейкпоинте всё запоролось. Чтобы этого не было идем Xcode -> Behaviours -> Edit behaviours... Далее нам нужна секция Running -> Pause.
Выбираем Show tab named, пишем туда наше уникальное название вкладки, например Debug (замечу, что при повторных запусках вкладка не дублируется), в конце ставим active window — это, как ни странно, открывает новую вкладку (Cmd + T) для нашего отладчика. Ешё я скрыл ненужную правую панель — но это по желанию. Вообще кастомизация среды при разных условиях — это в разделе Behaviours
Всё, мы настроены, двигаем дальше.
Управляем отладчиком
Добавляем сразу в копилку полезных hotkey'ев кто не знал Cmd + Shift + C. Это быстрый переход в консоль.
Сmd + Shift + Y — это скрыть/показать консоль.
Наши новые команды:
Step Into — step или коротко s
Step Over — next или коротко n
Step Out — finish
Continue — continue или 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:
Знакомо? А где же наши 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 страны, отлично, как нам быстро найти Россию?
Знаю, вот так:
(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 Into — step или коротко s
Step Over — next или коротко n
Continue — continue или c
Отключить все брейкпоинты — breakpoint disable
Вот тут весь список команд LLDB. - Obj-c не видит свойства объекта Swift-ового класса? Не беда:
(lldb) po [array[0] name]
- В окне variables 242 ключа у словаря?
Ищем быстро:
(lldb) po [[[_countries objectForKey:@"RU"] title] string] Россия
- Ставим chisel и расширяем набор команд LLDB
Всем спасибо, кто прочитал. Надеюсь, пару приёмчиков кто-то для себя новых открыл.
Описал каждую из возможностей не подробно, но старался показать направление, кому нужно — может разобраться глубже или спросить в комментариях!
Комментарии (13)
StrangerInRed
17.11.2015 10:31-3Первый и самый важный приемчик, который вы должны открыть в этой статье для себя: ставишь AppCode, не заморачиваешься с debug-crab-style.
corristo
17.11.2015 14:39+2у AppCode своих проблем вагон.
AnthonyBY
17.11.2015 19:41ох, я надеялся что вы вытяните самый сок из курса 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 (которые срабатывают лишь при определённых условиях или значениях)
egormerkushev
18.11.2015 20:21Вопрос по теме: последнее время постоянно 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».katleta
18.11.2015 21:49Судя по всему у вас стоит оптимизация, или вы strip debug symbols. В общем, посмотрите внимательно, например, такой линк. Тут, вероятно, ваша проблема и её подробное решение
egormerkushev
19.11.2015 13:06Действительно, по недосмотру стояла схема релизная. Как-то и не заметил. Гуглил — всё не то было… Спасибо.
VYakushev
Первый и самый важный приемчик, который я в этой статье для себя открыл — это как можно в XCode посмотреть возвращаемые значения для сообщений, да и вообще произвольную информацию в текущем контексте. После IDEA очень не хватало такой возможности и думал, что в XCode её нет. Спасибо!