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

автор Nikolay July

19 Сен 2012

Часто разработчики предпочитают использовать уже готовые решения задач в своих приложениях — так быстрее, дешевле и логичнее. Но не всегда. Сегодня я расскажу, как мы искали неуловимый баг в 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), и, возможно, они его скоро исправят. Но на момент написания статьи проблема существовала, о чем мы вас и предупредили, а кто предупрежден — тот вооружен.

  • 0 Репосты

Комментарии

Фильтр

Закрыть

Технологии

Индустрии