Загадочный баг в MKReverseGeocoder. Следствие ведут iOS-разработчики

IT-копирайтер
Время чтения: 5 минут
Часто разработчики предпочитают использовать уже готовые решения задач в своих приложениях — так быстрее, дешевле и логичнее. Но не всегда. Сегодня я расскажу, как мы искали неуловимый баг в Geocoder (SDK для iOS 4.3.x), возникающий при работе с геолокационными сервисами, и как мы его победили, доказав, что иногда лучше применять собственные решения.
Итак, компания Азофт работала над очередным крупным iOS-проектом. Из технических особенностей стоит отметить то, что проект подразумевал частое общение с сервером и предвещал много фаз. Поэтому было принято решение использовать на сервере Protocol Buffers (сокр. Protobuf) от Google, позволявший, помимо всего прочего, облегчить поддержку старых версий iOS.
Другой важный момент — это наличие в приложении функции звонков на бесплатный номер службы поддержки. При этом было одно существенное «НО»: для звонков по России нужно набирать 8-800-…, а для звонков из других стран — набирать +7-800-…
Чтобы пользователям не приходилось задумываться о нюансах с номерами, мы решили использовать класс MKReverseGeocoder iOS-фреймворка MapKit, позволяющий приложению определять страну пребывания и выбирать нужный номер.
Все складывалось отлично: приложение было написано и успешно протестировано не один раз — багов не зафиксировано. Все было готово к релизу.
Однако в один «прекрасный» день нам приходит сообщение от клиента о падении приложения при тестировании. Причем падение критичное, даже не позволяющее продолжать тестирование: ошибка возникала в произвольный момент, после чего приложение не запускалось в течение некоторого времени. Затем снова работало. Клиент отметил, что приложение вело себя так на iPhone 3GS и iPhone 4 почти у всех тестеров.
При этом, сколько бы раз мы сами ни запускали приложение, падение не повторилось. Чтобы разобраться в странной ситуации, мы запросили у клиента крэшлоги.
0 libobjc.A.dylib 0x3597ec98 0x3597c000 + 11416 1 ProtocolBuffer 0x32a26fb8 0x32a24000 + 12216 2 ProtocolBuffer 0x32a26cea 0x32a24000 + 11498 3 ProtocolBuffer 0x32a280f0 0x32a24000 + 16624 4 Foundation 0x31162230 0x31151000 + 70192 5 Foundation 0x31162138 0x31151000 + 69944 6 CFNetwork 0x30ddb576 0x30dcd000 + 58742 7 CFNetwork 0x30dd0fb2 0x30dcd000 + 16306 8 CFNetwork 0x30dd10ca 0x30dcd000 + 16586 9 CFNetwork 0x30dd0e34 0x30dcd000 + 15924 10 CFNetwork 0x30dd0de6 0x30dcd000 + 15846 11 CFNetwork 0x30dd0d58 0x30dcd000 + 15704 12 CFNetwork 0x30dd0cd6 0x30dcd000 + 15574 13 CoreFoundation 0x34982a72 0x3490d000 + 481906 14 CoreFoundation 0x34984758 0x3490d000 + 489304 15 CoreFoundation 0x349854e4 0x3490d000 + 492772 16 CoreFoundation 0x34915ebc 0x3490d000 + 36540 17 CoreFoundation 0x34915dc4 0x3490d000 + 36292 18 GraphicsServices 0x31db5418 0x31db1000 + 17432 19 GraphicsServices 0x31db54c4 0x31db1000 + 17604 20 UIKit 0x350ccd62 0x3509e000 + 191842 21 UIKit 0x350ca800 0x3509e000 + 182272 22 MyApp 0x0000b7b8 0x1000 + 42936 23 MyApp 0x000021d4 0x1000 + 4564
Крэшлог показал, что система использовала библиотеку Protobuf в обход нашего приложения, что казалось невозможным. Мы просмотрели весь код: к Protobuf просто нельзя было обратиться в обход наших классов. В еще больший тупик ставил тот факт, что мы не могли воспроизвести сам крэш.
Через некоторое время, после безрезультатных попыток отловить ошибку, мы попросили клиента запустить приложение и показать лог или консоль девайса. Но в течение полуторачасового звонка по Скайпу он тоже не смог воспроизвести загадочное падение, что еще сильнее озадачило нашу команду.
Прошел месяц поисков ошибки и безуспешных попыток воспроизвести падение на нашей стороне. Мы решили вновь созвониться с клиентом с той же целью — вдруг на этот раз повезет? И нам, действительно, повезло: программа упала! Казалось бы, в любой другой ситуации, было бы досадно, что почти готовое приложение неожиданно и столь сокрушительно падает. Но мы были несказанно рады: наконец мы стали свидетелями этого события, наконец появился шанс выяснить, что работает не так.
Вот что мы увидели: в консоли появилось предупреждение от Geocoder. Как мы узнали позже, оно возникло из-за того, что используемый IP на несколько часов попадал в бан Google Maps за слишком частые обращения к сервису. Но непосредственная причина падения на iOS 4.3.x была в другом: сообщение о возникшей ошибке почему-то не приходило на Geocoder, и он, соответственно, не мог ее адекватно обработать.
Также мы выяснили, какая именно структура системы использовала Protobuf в обход нашего приложения. Это был фреймворк MapKit, общавшийся через Protobuf с Google Maps.
Почему ошибка оказалась такой неуловимой? Все дело в том, что лимит Google Maps — 2500 запросов, допускаемых с одного IP. Если у вас «белый» IP или IP-адрес используетcся лишь в одном офисе, то вероятность превысить лимит крайне мала и повторить падение невозможно, что с нами и случилось. Однако, в центре мегаполиса при подключении через Wi-Fi на один «белый» IP попадают тысячи человек, и вероятность падения значительно увеличивается, что периодически и происходило.
Несмотря на длительные поиски таинственного бага, проект был успешно завершен и сдан вовремя: чтобы приложение работало без ошибок, мы решили отказаться от MKReverseGeocoder, а вместо него написали собственный кастомный запрос к Google Maps.
Apple так официально и не подтвердили существование ошибки в Geocoder’е (SDK для iOS 4.3.x), и, возможно, они его скоро исправят. Но на момент написания статьи проблема существовала, о чем мы вас и предупредили, а кто предупрежден — тот вооружен.
Комментарии