Cocoa / iPhone: отладка ошибки EXC_BAD_ACCESS

By | December 22, 2012

Если вы хоть раз занимались разработкой программного обеспечения под iPhone, то наверняка встречались с ошибкой EXC_BAD_ACCESS. Отловить ее достаточно непросто, т.к. встроенный дебагер не покажет вам нужный стектрейс и тем более не подскажет, как все исправить.

Что же такое EXC_BAD_ACCESS? Мне удобнее сравнивать эту ошибку с NullPointerException в Java, т.к. возникает она в ситуации, когда вы отправляете “сообщение” объекту, который уже был выгружен из памяти (released). Например, попробуйте создать объект с помощью alloc и затем два раза подряд его “отпустить” с помощью release.

Так как же быть, если у нас достаточно большой проект с массой кода и объектов? Как проще и быстрее отловить данную ошибку? На помощь придет директива NSZombiEnabled, установленная в переменных окружения проекта. Все, что она сделает – это заставит Objective C runtime оставлять объект-пустышку за каждым выгруженным реальным объектом и в следующий раз, когда вы попытаетесь обратиться к своему выгруженному объекту, вызов пойдет именно к пустышке, которая остановит выполнение кода и выведет короткое и ясное сообщение в дебагер о том, какой объект был вызван и что за сообщение ему было отправлено. Стектрейс при этом также не будет испорчен, и сможет дать дополнительную информацию о месте баги.

Чтобы установить данную переменную, откройте Xcode, меню Project –> Edit Active Executable. Далее выберите вкладку Arguments, во втором списке добавьте переменную NSZombieEnabled и поставьте ей значение YES в столбце Value. Убедитесь, что перед названием переменной стоит галка.

В моем примере помимо NSZombieEnabled установлены еще две переменные: NSAutoreleaseFreedObjectCheckEnabled и NSDebugEnabled. Первая предотвратит автоматическую выгрузку из памяти объектов, помеченных как autorelease в случае, когда они уже были выгружены ранее. Вторая переменная будет полезна скорее самому разработчику, т.к. ее можно будет использовать в своем коде, когда нужно, например, вывести дополнительную информацию в debug log:


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