iOs Debug Master

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

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

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

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

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


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

b19393b29a7e453487f0baf590325fd3.png

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

ffe1b5348de04c2c83a8dc53c88a0e94.png

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

3ada1b1b7c4f4a8ebe47222aa0902ab1.png

Спасибо моему коллеге: он осуществил мою давнишнюю мечту — открывать консоль отладчика в новом окне. А то всегда напрягало, что только открыл и настроил вкладки как тебе надо, так сразу же при первом брейкпоинте всё запоролось. Чтобы этого не было идем Xcode → Behaviours → Edit behaviours… Далее нам нужна секция Running → Pause.
Выбираем Show tab named, пишем туда наше уникальное название вкладки, например Debug (замечу, что при повторных запусках вкладка не дублируется), в конце ставим active window — это, как ни странно, открывает новую вкладку (Cmd + T) для нашего отладчика. Ешё я скрыл ненужную правую панель —, но это по желанию. Вообще кастомизация среды при разных условиях — это в разделе Behaviours

image

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

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


Добавляем сразу в копилку полезных 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 и каждый сам вправе решить использовать ему подобный подход или нет и какие команды нужны и интересны. Остановлюсь на том, что показал куда смотреть.
Удобство выполнения команд еще в том, что есть привычный как в терминале переход к предыдущей/последующей команде через стрелки вверх-вниз.
В результате наша работа с отладчиком выглядит вот так:

145d01d8778a47f3a4e2a80d5b72e9c8.png

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

)

(lldb) po [array debugDescription]
<__NSArrayI 0x7f9a1878c6b0>(
,
,

)

(lldb) po array[0]


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


(lldb) po [[[_countries objectForKey:@"RU"] title] string]
Россия


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

Магические UIView


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

(lldb) po self.view.subviews


увидим что-то вроде такого:

; layer = ; contentOffset: {0, -180}; contentSize: {375, 843}> collection view layout: ,
>,
>


Сразу видим набор 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()
    }
}


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]
    Россия
    
    
  • Облегчаем себе отладку UIView — чуть выше summary есть немного полезного кода

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

© Habrahabr.ru