Начинающим разработчикам: история одного бага

В 66-ом выпуске подкаста мы слегка затронули тему о «легкости перехода» на новый SDK, выход которого состоялся вместе с анонсом iOS 6. Мы так же пообещали, что на страницах ресурса AppleInsider.ru появится статья одной из ведущих подкаста — Ксении Покровской (iOS разработчик компании Parallels), которая и приоткроет завесу над некоторыми аспектами такого перехода.

Редакция

Совсем недавно Apple представила бету новой версии iOS, которая, как обычно, содержит много «вкусных» возможностей. Но наряду с приятным возникает вопрос: как уже выпущенные приложения будут работать на новой версии iOS? Вообще тема работы старых приложений на новой версии мобильной ОС упоминается, к сожалению, нечасто. И мне хотелось бы поделиться своей историей.

d17c669455d3242105afc4569cf9f90c

В статье краткое описание «граблей», на которые мне довелось наступить при подготовке новой версии продукта Parallels Mobile с поддержкой iOS 5, и способов эти грабли обойти. Также мы на примере увидим «особое» отношение Apple к теме обратной совместимости в iOS SDK, ну и пару советов, как минимизировать проблемы при переходе на новые версии мобильной ОС.

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

Ситуация непростая, и единого решения тут нет. Поэтому рассмотрим пример реальной проблемы, с которой мы столкнулись при выпуске обновления Parallels Mobile: при старте приложения (и при возврате из фонового режима работы) состояние элемента UISegmentedControl не совпадает с содержимым страницы. Ситуация показана на рисунке ниже:

65146b2f103510b97c29646c1775bf13

Если переключать вкладки UISegmentedControl’а руками, а не выставлять нужную программно, все работает правильно.

Изучение всех компонентов, которые так или иначе могут влиять на UISegmentedControl и содержимое страницы, закончилось ничем: получалось, что никакие изменения проекта не связаны с кодом, обеспечивающим изменение содержимого страницы в зависимости от состояния UISegmentedControl. Дальнейшие эксперименты показали, что версия, собранная из тех же исходников, что и приложение в App Store, тоже содержит упомянутый баг. Но как программа, собранная из одних и тех же исходников, может работать по-разному на одной версии iOS? (Для полноты эксперимента тесты приложения: старой версии из App Store и новой пересобранной версии проводились на одном и том же iOS-устройстве).

Исследование глобальной проблемы начнем с конкретной задачи: рассмотрим подробнее UISegmentedControl. Чтобы отслеживать переключение вкладок, нужно зарегистрировать обработчик события UIControlEventValueChanged следующим образом:

[self addTarget:self action:@selector(didValueChanged:) forControlEvents:UIControlEventValueChanged]; 

В функции-обработчике didValueChanged происходит изменение содержимого страницы в соответствии с состоянием UISegmentedControl.

Дальнейшее изучение этого UI Control в документации Apple показало, что начиная с iOS 5.0, программное изменение состояния больше не генерирует событие UIControlEventValueChanged. Это значит, что в новых версиях iOS для изменения содержимого страницы необходимо явно генерировать это событие, если выделение сегмента делается не пользователем, а в коде приложения.

Событие можно генерировать сразу после установки номера выделенного сегмента и лучше предварительно проверять версию iOS, иначе на старых версиях операционной системы событие UIControlEventValueChanged будет генерироваться дважды:

[self setSelectedSegmentIndex:ind]; if (SYSTEM_VERSION >= 5.0) [self sendActionsForControlEvents: UIControlEventValueChanged]; 

Представленный код решает проблему, но оставляет открытым очень важный вопрос: почему версия из App Store работает по-старому на новой iOS 5.1?

Ответ из документации Apple«As a backward-compatibility mechanism, Apple frameworks sometimes check for the version of the SDK an application is built against, and, if it is an older SDK, modify the behavior for compatibility. This is done in cases where Apple predicts or discovers compatibility problems.»

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

  1. тщательно изучайте изменения в iOS API, особенно если выходит мажорное обновление операционной системы;
  2. проводите полное регрессионное тестирование при переходе на новый iOS SDK;
  3. расширяйте набор регрессионных тестов. Это позволит обнаружить проблему при переходе на новую версию iOS, а значит сузит область поисков решения и сократит время решения проблемы.

Ну и в заключение надо сказать, что Apple – один из немногих вендоров, позволяющих себе изменять поведение элементов платформы (UI Control’ов, функций и т.д.) между версиями платформы. Обычно функциональность существующих компонентов расширяется: появляются новые параметры для более гибкого управления элементом. Либо появляются новые элементы с другим поведением «по умолчанию», а старые компоненты объявляются устаревшими (deprecated) и удаляются в новых версиях платформы. Почему Apple иногда выбирает путь революции, а не эволюции — непонятно, но все, что нам остается – это переходить на Android минимизировать свои проблемы при переходе на новую iOS.