Облегчаем поддержку iOS приложения. Часть 1 — не отрываясь от Xcode

в 19:54, , рубрики: iOS, xcode, отладка, разработка под iOS

Добрый день. Я хотел бы рассказать о том, как можно облегчить поддержку iOS приложений.

Всем, кто создавал iOS приложение и оно доходило хотя бы до открытого β-тестирования, скорее всего знакома фраза: «Я тут поигрался с приложением и вот что получилось...». После этой фразы вы могли провести несколько часов, пытаясь понять, как же «это» получилось.

Если вам знакома эта ситуация или хочется узнать о том, как спасти себя от такого в будущем — прошу под кат.

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

Итак, хит парада — трудно воспроизвести или трудно дебажить

То есть, либо до проблемы трудно добираться (нужно далеко забраться в приложения с очень особыми условиями, да хоть погода за окном должна быть именно -40, а локация ваша должна быть на экваторе), либо надо много раз повторять одно и тоже действие. В последнем случае, то, что вы на каждой итерации попадаете в breakpoint, как-то раздражает, но именно на этой строке кода должно что-то произойти, нам очень надо остановиться именно на ней, когда нам повезет воспроизвести на баг.

Все, кто разрабатывает приложения для iOS/MAC, обязательно ставили брейкпоинты и пытались понять, что же происходит перед тем, как приложение падает/ведет себя некорректно. К сожалению, зачастую разработчики используют малую часть функционала, который им доступен. Перерыл Хабр и нашел всего одну статью об полнофунциональном использовании breakpoint’ов. Про отладку приложения с помощью lldb вообще глухо (ну или мои поисковые способности не позволили найти нужную статью).

Брейкпоинты

Отличная статья, но я не увидел там описания Symbolic breakpoint. Так что рекомендую ознакомится со статьей, а я расскажу о Symbolic breakpoint'ах.

Итак, вы смогли получить тот самый запуск, который воспроизводит некорректное поведение. И, допустим, оно происходит, когда UIViewController только появился, но вот незадача — у вас неу импелементированного метода -[MyViewController viewDidAppear:] и нет ни делегата у UINavigationViewController, ни чего-то похожего в вашем коде, что помогло бы поставить breakpoint в нужный момент. Именно в этой ситуации вам и пригодятся Symbolic breakpoint.

Облегчаем поддержку iOS приложения. Часть 1 — не отрываясь от Xcode - 1

Вам лишь надо вбить символ -[UIViewController viewDidAppear:]

Облегчаем поддержку iOS приложения. Часть 1 — не отрываясь от Xcode - 2

И вы остановитесь в нужный момент.

Еще одно очень полезное применение Symbolic breakpoint — вам достался проект от другого разработчика и есть описание проблемы от тестировщика. Вы понимаете, как попасть туда в приложении, но как сопоставить это с кодом? База кода может быть колоссальной, и анимация намекает, что происходит -[UINavigationController pushViewController:animated:], но вот где это происходит — не ясно. Добавляем Symbolic breakpoint: -[UINavigationController pushViewController:animated:] и мы остановимся на всех вызовах этого метода.
Однако при использовании Symbolic breakpoint для системных функций у вас нет ни self, ни _cmd. Ничего, что обычно доступно. Так что вам придется подбираться к конроллеру снаружи и поможет нам lldb.

LLDB

Во время запука приложения нажмите на паузу — видите (lldb)? Это консоль дебагера. Есть много полезных команд, которые он умеет выполнять — и самая первая — это help. Теперь можете увидеть остальные полезные команды.
Самая популярная — это po(print object). Она распечатывает -[NSObject debugDescription], в отличии от NSLog, который выводит -[NSObject description]. Стоит иметь это ввиду, когда вы переопределяете description для более удобного логирования в приложении — не забудьте еще и debugDescription.

Продолжим с того места где остановились: у вас есть консоль и вы в нужном месте приложения. Но как минимум надо получить полную иерахрхию UIView и неплохо бы все ivar'ы MyViewController?
Чтобы узнать указатель на UIWindow нашего приложения, нам надо набрать:

po [(MyAppDelegate *)[UIApplication sharedApplication].delegate window]

Мы получили указатель на UIWindow, давайте теперь получим указатель на rootViewController:

po [(UIWindow *)<pointer> rootViewController]

Теперь у нас есть указатель на главный контроллер. Аналогичными нанипуляциями мы можем дойти до нужного нам котроллера, идя по child'ам или же по property'ям.

Итак, у нас есть указатель на наш MyViewController. Допустим, нам нужен его целочисленный _ivar, property для него никто не создавал (незачем над лишние getter и setter).

Набираем

po ((MyViewController *)<pointer>)->_ivar

Теперь у нас есть все, что нужно.
Вывод информации об объектах только часть функционала. Вы хотите, чтобы на девайсе анимация стала медленнее, как в симуляторе по нажатию Toggle Sow Animations? Вот вам вариант, как это можно сделать, не перезапуская приложение:

expr -- ((CALAyer *)[(UIWindow *)[(MyAppDelegate *)[UIApplication sharedApplication].delegate window] layer]).speed = 0.2

Готово. На время этого запуска у вас медленные анимации и вы можете так делать именно в нужный момент времени и именно в этот запуск. Как надоест — верните значение 1.

Может, эти примеры наивны, но они дают понять базовый функционал. И да, помните, я описал невероятный расклад — -40 && экватор. Теперь вам никто не мешает подменить значения на лету перед выполнением критичного куска кода и все будет выглядеть, как-будто вы на экваторе и за окном у вас -40.

chisel

Есть один большой минус в предыдущем описании — очень долго и дорого идти до нужного контроллера. Но есть проект, который вам поможет — chisel. Этот товарищ многое умеет, список команд можно найти тут. Если у вас непонятно как лежит вью, потому что она прозрачная, то можно подсветить границы (к примеру, командой mask). Можно, зная текст на кнопке, найти ее (команда pa11y). А если заранее подготовится и сделать так, что текст accessibilityLabel соответсвует тексту элемента, то искать можно что угодно. После установки chisel список его команд добавится в полный список того, что умеет lldb.

Для меня хит парада — это pviews и pvc. Эти команды выведут иерархию UIView и UIViewControllr. Если вдруг нужно понять, что за команда и как она работает — набираем help <имя команды>.

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

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

Attach to Process

Предположим, что хороший задел для дебагинга у нас есть, мы поставили chisel и теперь можем в нужный момент в lldb набрать help и благодаря вшитым и доп. командам понять, что же у нас не так — вот именно тут и на этом запуске. Все это мы можем сделать, пока наше приложение запущено из Xcode на девайсе или симуляторе. Но что же делать, если мы словили баг просто в процессе использования приложения на девайсе? Можно успеть подойти к Xcode и попросить его присоединиться к приложению. Для этого вам нужно Xcode→Debug→Attach to Process. Можно по имени приложения, но у меня Xcode сам угадывает target.

Заключение

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

Автор: NikolayJuly

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js