Noveo

Наш блог Advanced debugging в Xcode: средства отладки, про которые часто забывают

Advanced debugging в Xcode: средства отладки, про которые часто забывают

Noveo Advanced debugging Xcode iOS

Senior iOS-разработчик Noveo напоминает о доступных из коробки, но часто игнорируемых разработчиками средствах отладки кода в среде Xcode: продвинутое использование брейкпоинтов, влияние на состояние приложения, редактирование UI без перезагрузки и другие техники, которые помогут ускорить отладку приложения или поиск багов.

Intro

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

По данным некоторых исследований, мы в среднем тратим до 60% на отладку — и это именно усредненное значение, для кого-то, особенно для начинающих разработчиков, оно может быть ещё больше. Пост призван уменьшить это время и сделать процесс отладки эффективнее и приятнее.

Noveo advanced debugging Xcode iOS
Изначальный код

Давайте посмотрим на процесс изнутри. Я привёл немного странный пример, но он достаточно связан с ежедневной рутиной и отлично опишет большинство кейсов. Этот код производит вычисления высоты для ячейки таблицы. Высота зависит от некоторых констант и параметров. Допустим, в некоторых случаях высота рассчитывается неверно. Нашей задачей становится изучить эту функцию, например узнать значение флага showTitle в момент вычисления. Что первым приходит на ум? Правильно, поместить print для отладки.

Запускаем проект — он у нас большой, да и Xcode не идеальный. Чаще всего происходит всем знакомая ситуация: добавили одну строчку и ждём минуту, пока соберётся. А ведь кроме сборки и запуска нужно ещё и восстановить условия, добраться до нужного экрана, воспроизвести проблему и только после этого посмотреть вывод свежедобавленного print’a.

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

Noveo advanced debugging Xcode iOS
Код после экспериментов

И снова нас ждёт сборка, воспроизведение условий и анализ. Мне кажется, все через это проходили и, надеюсь, это притянутая за уши история, существующая лишь как вводная для этой статьи. Справедливости ради замечу, что отладка через print’ы сама по себе не является чем-то плохим. Порой это единственный способ отладить что-то в сложных ситуациях, например когда ошибки происходят в оптимизированном компилятором коде. Но сегодня разговор пойдёт о простых сценариях, которые чаще всего происходят во время разработки.

Breakpoints

Мы уже выяснили, что отладка через добавление вывода в консоль не совсем эффективна и нам нужен какой-то инструмент, который облегчит нам жизнь. Таким инструментом являются брейкпоинты! Все мы любим этот механизм, который представлен практически во всех средах разработки на любых платформах и языках, и пользуемся им. Где-то он реализован лучше, где-то хуже, но в целом Apple предоставила нам мощный и гибкий механизм точек останова. Однако, работая с разными людьми, я заметил, что пользуются ими в большинстве случаев исключительно для остановки программы — просто чтобы убедиться, что её выполнение пошло по запланированному сценарию. Иногда люди пользуются консолью отладки и командой po, но лишь в тех случаях, когда нужно разок выяснить состояние переменной. Я предлагаю рассмотреть дополнительные возможности отладчика, встроенного в нашу IDE, и привести примеры ситуаций, в которых они могут пригодиться.

Conditional breakpoints

Noveo advanced debugging Xcode iOS
Окно редактирования брейкпоинта

Начнём с очевидного: conditional breakpoints. Как ни странно, строка, позволяющая указать условия срабатывания брейкпоинта, всегда у нас перед глазами, но почему-то люди удивляются такой возможности. Для удивлённых самим наличием такого диалога — его можно увидеть, если дважды нажать на сам брейкпоинт. В эту строку можно записать любое выражение, которое может вернуть булево значение, будь то сравнение переменной из текущей области видимости или вовсе значение какого-то синглтона. Но будьте внимательны — медленно вычисляемое выражение способно существенно снизить производительность вашей программы.

Skipping

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

Actions

Noveo Advanced debugging Xcode iOS
Нажатие на Add Action

Но самое интересное дня нас кроется за кнопкой Add Action. Эта кнопка позволяет добавить дополнительное действие, которое будет вызвано в момент срабатывания брейкпоинта. Как вы видите, есть 6 типов действий, которыми можно дополнить брейкпоинт:

  1. Apple script. Позволяет запустить скрипт на одноименном языке.
  2. Capture GPU Frame. Для отладки приложений, использующих движок Metal, может потребоваться эта опция.
  3. Debugger command. Позволяет выполнить команду отладчика. О ней мы поговорим позже.
  4. Log-message. Позволяет вывести текстовое сообщение в лог.
  5. Shell command. Позволяет выполнить произвольную команду в среде, дефолтной для системы командной оболочки, sh/bash/zsh.
  6. Sound. Позволяет проиграть звук из динамиков компьютера, на котором запущен Xcode.

Я не буду рассказывать о первых двух типах — они слишком специфичны и вряд ли вам пригодятся. А ещё пропущу последний пункт, так как особо рассказывать там нечего. Но помнить о нём стоит — он может вам пригодиться, например, когда нужно быстро совершить некое действие в приложении вслед за триггером, которым и может являтся звук от брейкпоинта, поставленного в нужное место.

Log message

Noveo Advanced debugging Xcode iOS
Редактирование брейкпоинта с экшеном Log Message

Рассмотрим чуть более подробно тип дополнительного действия «Log message». Если мы его выберем, к нашим услугам окажется строка ввода формата сообщения. Обратите внимание, что в строке можно указывать полезные плейсхолдеры, два из которых позволяют подставить информацию о брейкпоинте и одно, самое полезное, позволяет подставить результат вычисления произвольного выражения. Таким выражением может быть переменная или любая другая конструкция используемого вами языка программирования. Но это не имеет никакого смысла, если не поставить галочку «Automatically continue after evaluating actions». Именно она в паре с любым из действий позволит нам экономить время на дебаге. Больше не нужно писать print(), пересобирать проект и ждать вечность. В любой момент времени, без перезапуска проекта вам доступен вывод в консоль отладки любой информации о ходе выполнения программы. А для знающих толк в извращениях дебаге Apple предусмотрела возможность воспроизвести выражения, используя встроенный синтезатор речи.

Shell command

Noveo Advanced debugging Xcode iOS
Редактирование брейкпоинта с экшеном shell command

Нетрудно догадаться, что этот экшен позволяет запустить произвольную команду в стандартной оболочке терминала ОС. Как и «Log message», она позволяет вычислить результат выражения в текущем контексте и дополнить им аргументы вызова команды. Для чего это может быть полезно? Примеров использования можно придумать массу. Из реальной жизни: запуск троттлинга через Charles. Необходимо было замедлять запросы из определённой точки, при этом в остальное время соединение должно было быть полноценным. Я не успевал включать-выключать троттлинг вручную и ещё совершать действия в симуляторе. Такой трюк с брейкпоинтом и «Shell command» отлично меня выручил. В другой раз мне понадобилось изменять информацию на сервере прямо параллельно с запросом, чтобы отловить довольно странный баг. Тут тоже был кстати этот вид брейкпоинта. Особые извращенцы могут собрать конструкцию на Arduino с электрошокером и бить себя током при каждом срабатывании нежелательного кода. Шучу. Не пытайтесь это воспроизвести в реальной жизни.

Debugger command

Noveo advanced debugging Xcode iOS
Редактирование брейкпоинта с экшеном Debugger command

Одним из самых интересных видов экшенов я считаю «Debugger command». Этот экшен позволяет действительно безгранично влиять на отлаживаемую программу. Debugger command — это команды отладчика LLDB, а LLDB — это отладчик для проекта LLVM, который сейчас используется Apple и Xcode для сборки программ. Отладчик LLDB позволяет подключаться к процессу, прерывать выполнение программы и воздействовать на её память. Для этого отладчик имеет множество команд, некоторые из которых станут героями сегодняшнего повествования. Именно благодаря отладчику LLDB у нас в принципе есть такая замечательная возможность отлаживать программу, в частности устанавливать брейкпоинты. Начнём мы с самой известной команды — po. Наверняка многие из вас уже не раз использовали эту команду при отладке, но для меня в своё время это стало открытием, хотя я уже имел некоторый опыт в разработке под iOS на тот момент. Po — это сокращение от print object. Команда позволяет вычислить выражение из правой части от команды и распечатать в консоли результат выполнения. При этом у объекта запросится его debugDescription, если он определён, или просто description, если нет. У po существует команда-прародитель — print, или p, которая точно так же вычислит выражение и распечатает результат, но только в этом случае вам будет доступна сырая информация об объекте или скалярном типе. Обе эти команды будут компилировать введенное выражение в текущем контексте, что неминуемо замедлит выполнение кода при срабатывании брейкпоинта. К счастью, в Xcode 10.2 Apple добавили ещё одну команду отладчика — v, которая работает значительно быстрее. Она позволяет вывести в консоль значение переменной из текущей области видимости, но, в отличии от p и po, без компиляции выражения. Естественное ограничение, накладываемое этой особенностью, — вывод в консоль возможен только для хранимых свойств.

Noveo Advanced debugging Xcode iOS
Пример использования команды «v» и её ограничений

Affecting execution flow

Такая комбинация (брейкпоинт + debugger command po + автоматическое продолжение) заменит нам описанную ранее Log message. Что же ещё мы можем сделать с помощью такой комбинации? Например, с помощью дебаггера мы можем пропустить выполнение нескольких строчек кода, будто они закомментированы. При этом вам не нужно пересобирать программу и заново воспроизводить условия. Для этого достаточно ввести thread jump --by 1 для скачка вперёд на одну строчку или же thread jump --line 44 для перехода, как вы уже могли догадаться, к 44 строчке. Но будьте осторожны — вы не можете на 100% безопасно перепрыгивать по строчкам. Дело в том, что вы можете перепрыгнуть через инициализацию некоторой переменной, и это вызовет краш. Дело осложняется тем, что Swift «ленив» по своей природе, и инициализация может происходить не там, где вам кажется. Плюс компилятор при сборке вашей программы вставляет дополнительные инструкции, например для управления памятью, пропуская которые вы рискуете получить в лучшем случае утечку, в худшем — краш.

Affecting debugger

Noveo Advanced debugging Xcode iOS
Редактирование брейкпоинта с введенной командой

Кроме влияния на вашу программу, с помощью отладчика вы можете влиять на сам отладчик. Например, мы можем поставить брейкпоинт из брейкпоинта. Вы спросите, зачем это нужно? Бывают методы общего назначения, которые срабатывают по ряду триггеров. Например функция по отправке сообщения в аналитику может вызываться сотню раз в секунду, а нам нужно отловить именно ту отправку, которую породит нажатие на кнопку. В этом случае мы можем поставить брейкпоинт на метод нажатия кнопки и добавить команду установки брейкпоинта на произвольной строке программы в произвольном файле. Команда bp s -o -f Calc.swift -l 44 расшифровывается как breakpoint set one-shot на файл Calc.swift на строку 44. Модификатор -o или —one-shot создаст специальный тип брейкпоинта, который «живёт» ровно до момента своего срабатывания, а после исчезает. Таким нехитрым способом мы можем создавать интересные алгоритмы установки брейкпоинтов для отладки нетривиальных багов.

Other breakpoints types

Noveo Advanced debugging Xcode iOS
Панель переключения видов левой функциональной колонки Xcode c открытым Breakpoint Navigator и несколькими брейкопинтами

А есть ли ещё виды брейкпоинтов, о которых мы можем не знать? Конечно, есть. Xcode позволяет добавить некоторые виды брейкпоинтов, которые не относятся к какому-то конкретному файлу и строке. В Xcode есть вкладка Breakpoint Navigator, которая позволяет управлять уже созданными брейкпоинтами сквозь все файлы проекта, а также создавать новые. Внизу окна нашего IDE есть кнопка со значком плюса.

Noveo Advanced debugging Xcode iOS
Нижняя функциональная панель леовой колоки Xcode при открытом Breakpont Navigator

Это позволяет использовать 6 дополнительных типов брейкпоинтов:

  • Swift Exception брейкпоинт — брейкпоинт, останавливающий программу при срабатывании не перехваченного throw для Swift кода.
  • Exception брейкпоинт — то же самое, но для мира ObjC. Может показаться, что это не актуальный в современном мире брейкпоинт, но это не так. Стоит помнить, что нам пока всё ещё нужен UIKit, написанный на ObjC, ошибки которого мы можем отловить с помощью такого вот брейкпоинта.
  • Symbolic breakpoint — позволяет останавливать процесс выполнения программы при выполнении кода, ассоциированного с некоторым идентификатором, который Apple называет символом. О символах я расскажу чуть позже.
  • OpenGL ES Error брейкпоинт — брейкпоинт, останавливающий программу при возникновении ошибки OpenGL при разработке соответствующих приложений.
  • Constraint Error breakpoint — очевидно, остановит вашу программу при возникновении ошибки автолейаута.
  • Test Failure breakpoint может вам помочь при отладке тестов.

Так как уместить в этой сессии обзор всех типов точек останова не представляется возможным, я остановлюсь только на самых часто используемых. По своему опыту — я всегда использую Exception breakpoint. Довольно часто при разработке программ я сталкиваюсь с перехваченными системными исключениями, отладить которые порой проблематично из-за крайне неинформативного call stack’а. Думаю, вы сталкивались хоть раз с такой или подобной ошибкой:

Noveo Advanced debugging Xcode iOS
Сообщение в Debugger Console при падении приложения из-за не перехваченного исключения ObjectiveC

Exception breakpoint

Для того, чтобы сделать стек вызова более информативным, мы можем добавить Exception breakpoint. Он позволит остановить программу прямо на моменте выброса исключения и отследить цепочку событий, которые привели к такому результату. По умолчанию неперехваченное исключение вызовет аварийную остановку приложения, и в стеке вызова мы ничего полезного не увидим, т.к. исключение будет пробрасываться вверх по стеку вызова и вся информация о месте выброса будет утеряна. Exception breakpoint позволяет остановить программу в момент выброса исключения и уже привычными нами методами получить гораздо больше информации о проблеме, пройдясь по стеку вызова и просмотрев значения переменных, если это необходимо. Я считаю этот тип брейкпоинта очень полезным и использую его на всех проектах по умолчанию. Для этого в Xcode есть удобный механизм, который позволяет указать брейкпоинту уровень и хранить его на трёх уровнях:

  1. Проект.
  2. Воркспейс.
  3. Пользователь.

Просто нажмите на брейкпоинт правой кнопкой мыши и выберите Move breakpoint. Перенесённый на уровень пользователя, брейкпоинт будет доступен на всех проектах, какой бы вы ни открыли в вашем Xcode.

Symbolic Breakpoint

Noveo Advanced debugging Xcode iOS
Окно добавления Symbolic Breakpoint

Вторым часто используемым типом брейкпоинтов является Symbolic Breakpoint. Ранее я уже писал, что этот брейкпоинт позволяет останавливать программу при выполнении кода, ассоциированного с каким-то символом, и обещал рассказать подробнее про символы. Так вот, символы — это человекопонятные идентификаторы, которые ассоциируются с тем или иным адресом в памяти. LLDB умеет маппить известные ей символы в адреса функций и наоборот. При каждой сборке проекта система создаёт особый бандл из специальных файлов в формате dSYM, которые расшифровываются как Debug Symbols. Эти файлы хранят что-то вроде таблицы, содержащей в себе некоторые адреса методов и некоторые идентификаторы, среди которых сигнатуры методов, имена файлов, смещения и номера строк. Именно благодаря этим файлам мы можем поставить брейкпоинт на строку файла, получить читаемый стек вызова или расшифровать crashlog приложения из AppStore.

Благодаря этому механизму мы можем поставить брейкпоинт на любом методе класса, зная только его название. При этом нам не нужно достоверно знать, где этот метод объявлен и доступны ли вообще нам исходные файлы. Давайте рассмотрим реальный пример. Вас перевели на новый проект, и первая задача — исправить непонятное поведение на форме ввода данных кредитной карты, когда посреди набора фокус вдруг перепрыгивал на поле ввода имени. Сходу ничего не понятно, кода много, но симптомы ясны. Для расследования необходимо понять, кто и почему инициирует смену фокуса. Можно долго читать код, искать логику в неочевидных расширениях классов, а как надоест — сделать наследника UITextField’a, переопределив там метод becomeFirstResponder(), поменять реализации и уже там поставить брейкпоинт. А можно за 10 секунд создать символьный брейкпоинт -[UITextField becomeFirstResponder], и программа остановится в момент смены фокуса. По цепочке бэктрейса мы сможем легко восстановить последовательность событий, которые приводят к нежелательным результатам.

У тех, кто пользуется таким видом брейкпоинта в первый раз, наверняка возник вопрос: а что это за символ
-[UITextField becomeFirstResponder]? Это ObjectiveC-сигнатура метода установки текста для лейбла. Использование ObjectiveC обусловлено тем, что UIKit написан именно на этом языке. Пара слов для тех, кто имел мало опыта с ObjectiveC. Знак минуса обозначает, что нас интересует инстанс-метод, а не метод класса, далее в квадратных скобках записывается название класса и через пробел метод, двоеточие указывает на то, что этот метод принимает параметр. Тут можно возразить, что пример притянут за уши. Я согласен — в хорошем коде не будет десятка мест с установкой текста лейбла, но моя цель — показать, как это может работать. Давайте рассмотрим более реальный пример. Допустим, для целей отладки нам может понадобиться распечатать последовательность показа вью контроллеров. Добавляем брейкпоинт с символом -[UIViewController viewDidAppear:], указываем дополнительное действие po NSStringFromClass([instance class]) и, конечно же, не забываем поставить галочку «Automatically continue after evaluating actions».

Мы снова вынуждены использовать ObjC, даже в дополнительной команде, так как находимся в его контексте. Что касается Swift, то символы записываются как название ClassName.methodName(param:). Прописывать параметры не обязательно, LLDB попытается разрешить неоднозначность, если есть методы с одинаковым названием, но разными параметрами.

Рассказывая о символьных брейкпоинтах, я не могу не рассказать о возможности искать символы. Остановив программу любым способом, с помощью брейкпоинта или же просто нажав на пиктограмму паузы, мы можем воспользоваться командой image lookup -r -n и найти интересующие вас символы в вашей программе и во всех загруженных библиотеках. Это действительно делает вас чуть ли не богом дебага, потому как вы властны искать символы везде, скажем в UIKit’e, искать приватные методы, останавливать и изучать внутреннее устройство системных библиотек. Надеюсь, я убедил в вас в силе этого метода и он не раз поможет вам сэкономить время.

Noveo Advanced debugging Xcode iOS
Поиск приватного метода в UIKite c помощью image lookup

Watchpoints

Вотчпоинты позволяют останавливать программу, когда изменяется значение переменной. Корректнее будет сказать, что этот механизм позволяет следить за изменениями памяти по заданному адресу с заданным размером, но благодаря LLDB и Xcode разработчику достаточно сделать несколько кликов. Использование вотчпоинтов будет удобным, когда за изменением переменной не следует никакого сайд-эффекта прямо после изменения, но её состояние важно для отложенных вычислений. В ряде случаев может быть непонятно, что инициирует это изменение, и вотчпоинты позволят быстро узнать это. Достаточно приостановить выполнение программы в контексте нужного класса и воспользоваться окном Variables View. Тут будут перечислены переменные в текущем фрейме, доступные к отлаживанию. В крупных проектах вычисление доступных переменных и их типов может занимать некоторое время, поэтому иногда нужно подождать несколько (десятков?) секунд перед тем, как переменные будут доступны к манипуляциям над ними. Приятным бонусом является возможность «заглянуть» внутрь объектов Objective-C: функциональность Variables View позволяет увидеть приватные переменные этих объектов. По клику правой кнопки мыши по переменной нам доступно не так много опций — мы можем изменять значение переменных скалярных типов и, собственно, добавлять вотчпоинты.

Noveo Advanced debugging Xcode iOS
Левая колонка DebuggerView, с контекстным меню по одной из переменных

Конечно же, вотчпоинт можно установить и командой LLDB: watchpoint set variable <variable_name>, или, пользуясь функцией сокращения команд LLDB, просто: w s v <variable_name>, но помните, что переменная должна быть видна отладчику, то есть находиться в текущем фрейме. Помимо установки брейкпоинта на изменение переменной, нам доступна установка вотчпоинта на область памяти: watchpoint set expression — 0x0d78ab5ea8. В обоих случаях при изменении содержимого памяти по отслеживаемому адресу произойдет прерывание программы. Установленные точки останова можно посмотреть командой watchpoint list или в Debugger navigator. Так как любые вотчпоинты в итоге следят за адресом памяти, они становятся неактуальны после перезапуска и не сохраняются между перезапусками приложения. Даже если вы установили брейкпоинт на изменение переменной, под капотом механизм lldb вычислил её адрес и поставил вотчпоинт по этому адресу.

Affecting state

Будем закругляться. Последнее, о чем я хотел поведать в рамках этой статьи, — влияние на состояние приложения из LLDB. До этого я говорил только об изменении состояния какого-либо объекта системы при остановке по брейкпоинту. Но что, если нам требуется приостановить программу в произвольный момент времени? Нажатие на значок паузы приводит к приостановке программы, но вот вместо привычного нам кода мы увидим код ассемблера. Так как же добраться до произвольного объекта и выполнить с ним хитрые манипуляции?

Memory graph

Noveo Advanced debugging Xcode iOS
Панель инструментов отладчика Xcode с подсвеченной кнопкой Memory Graph

Большинство iOS-разработчиков уже с первых месяцев своей работы используют этот инструмент. Для тех, кто ни разу им не пользовался, поясню. Memory graph позволяет сделать дамп памяти программы и отобразить в виде списка и графа все экземпляры объектов, которые сейчас находятся в памяти. Зачастую этот инструмент используется для выявления утечек объектов и анализа связей, которые привели к такому результату. Но сегодня от этого инструмента нам нужна только возможность остановить программу в произвольное время, найти нужный объект и узнать его адрес. Но что мы можем сделать с этой, казалось бы, бесполезной информацией?

На самом деле — всё, что угодно. Тут нам на помощь приходит мощь ObjC. Мы можем написать [0x7fafffa54a5 setValue:[UIColor redColor] forKey:@”switchedOffColor”] — и мы уже поменяли значение цвета выключенной лампы на красный, используя стандартные методы NSObject, доступные нам из коробки. Но что, если нам недостаточно этих методов, а нужно «дёрнуть» за свои рычаги? Всё просто — мы можем использовать кастинг: [(MyLamp *)0x7fafffa54a5 powerOff]. Используя подобные техники можно воздействовать на любые сервисы, менеджеры и вью модели вашего приложения в любой момент времени. Мы можем сохранить значение этого адреса в переменную для удобства: (MyLamp *)$lamp = 0x7fafffa54a5. Важно, что название переменной должно начинаться со знака доллара. Это переменная будет жить до полной остановки программы, то есть ей можно пользоваться не только в текущем сеансе отладки, но и при следующем прерывании программы в рамках одного запуска. ObjectiveС предоставляет поистине широкие возможности для того, чтобы похакать текущее состояние и обойти многие ограничения, но что делать с классами, доступными только в Swift? Конечно же, при попытке кастинга Swift-класса в ObjC-контексте ничего не произойдёт. К счастью, в Swift есть подобный механизм. Точнее, функция, имя которой — unsafeBitCast. Мы вправе использовать его с адресом: unsafeBitCast(0x7fafffa54a5, to: MySwiftLamp.self) и получить экземпляр класса MySwiftLamp по адресу. Помните, её использование небезопасно, о чём нам намекает её имя, и её крайне осторожно нужно применять в коде приложения. Хотя, когда вам осознанно нужно будет использовать эту функцию, вы будете достаточно опытны для таких предупреждений.

View Hierarchy

Noveo Advanced debugging Xcode iOS
Панель инструментов отладчика Xcode с подсвеченной кнопкой View Hierarchy

Рядом со инструментом Debug Memory Graph соседствует другой, не менее полезный инструмент, — View Hierarchy. Он позволяет быстро найти нужную View, посмотреть её параметры и лейаут, посмотреть активные и неактивные констрейнты. С iOS 11 этот инструмент ещё научился отображать ViewController’ы в иерархии, таким образом находить нужную View стало легче. Неочевидным тут является возможность фильтрации по имени и возможность отключить/включить отображение View, скрытых за экраном. Также я обратил внимание, что редко кто пользуется панелью управления внизу окна визуального отображения View.

Noveo Advanced debugging Xcode iOS
Панель инструментов отладчика View Hierarchy

Кроме того, что она может регулировать глубину просмотра иерархии, она позволяет указать «включить отображение обрезанного контента» и «включать отображение констрейнтов». Обязательно поиграйтесь со всеми инструментами, я уверен — вы найдете полезное для себя применение для некоторых из них. Но в рамках этого рассказа нам нужна только возможность найти нужную View и узнать её адрес. Далее действуем по накатанной: po unsafeBitCast(0x7fafffa54a5, to: UIView.self), но в таком случае мы получим ошибку. Мы сейчас находимся в контексте ObjectiveC и не можем использовать po со Swift-кодом. Мы вынуждены использовать команду expession, или просто e с указанием языка: e -l Swift -- unsafeBitCast(0x7fafffa54a5, to: UIView.self), но и тут наши попытки не увенчаются успехом, мы получим ошибку error: <EXPR>:3:35: error: use of unresolved identifier 'UIView'. Это произойдет из-за модульной природы Swift’а. Для успешного выполнения операции нам потребуется сделать импорт модуля UiKit:

e -l Swift -- import UIKit, и после этого мы наконец добьёмся результата: e -l Swift -- unsafeBitCast(0x7fafffa54a5, to: UIView.self). Ура! Мы получили описание в консоли. Теперь давайте попробуем поменять, скажем, цвет её бэкграунда. Для начала сохраним View в переменную, чтобы облегчить процесс доступа к ней. Как и в случае с ObjectiveC, при создании переменной в LLDB контексте её название должно начинаться со знака доллара: e -l Swift -- let $view = unsafeBitCast(0x7fafffa54a5, to: UIView.self), далее мы можем применить необходимые изменения: e -l Swift -- $view.backgroundColor = .red. Чтобы увидеть изменения, необходимо продолжить выполнение программы. Но есть способ увидеть изменения и без этого, находясь в режиме «паузы». Дело в том, что мы не видим изменения не потому, что приложение приостановлено, а потому, что все изменения UIView копятся в транзакцию CALayer и применяются только в конце «вращения» текущего RunLoop’а с помощью вызова CATrasaction.flush(). Когда приложение приостановлено для отладки, операционная система всё ещё живёт своей жизнью, вы можете свернуть это приложение и открыть другое. Операционная система всё ещё опрашивает состояние UI вашего приложения и отрисовывает ваше приложение несколько десятков раз в секунду, только RunLoop приостановлен, CATrasaction.flush не вызывается, изменения не применяются. Да, достаточно сделать вызов e -l Swift -- CATrasaction.flush(), и мы увидим изменения.

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

НазадПредыдущий пост ВпередСледующий пост

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: