EXC_BAD_ACCESS на iPhone при использовании «obj != nil

У меня есть очень простая строка кода в Objective-C:

if ((selectedEntity != nil) && [selectedEntity isKindOfClass:[MobileEntity class]])

Время от времени и по неизвестной мне причине игра вылетает из-за этой строки кода с EXC-BAD-ACCESS. Обычно кажется, что это время, когда что-то удаляется с игрового поля, поэтому я предполагаю, что то, что было, selectedEntity освобождается, тогда это приводит. Помимо того, что невозможно выбрать существующие Entities (но кто знает, может быть, это не совсем так в моем коде...), тот факт, что я специально проверяю, есть ли есть selectedEntity перед Доступ к нему означает, что у меня не должно быть здесь никаких проблем. Предполагается, что Objective-C поддерживает логическое короткое замыкание, но, похоже, это не РЕДАКТИРОВАТЬ: похоже, что короткое замыкание не имеет ничего общего с проблемой.

Кроме того, я поместил @try/@catch вокруг этого блока кода, потому что я знал, что он время от времени взрывается, но, похоже, это игнорируется (я предполагаю, что EXC-BAD-ACCESS не может быть пойман).

Так что в основном мне интересно, знает ли кто-нибудь, как я могу поймать это и выбросить (потому что меня не волнует эта ошибка, пока она не приводит к сбою игры), или может объяснить, почему это может происходить . Я знаю, что Objective-C делает странные вещи со значением «nil», поэтому я предполагаю, что он указывает на какое-то странное пространство, которое не является ни указателем объекта, ни nil.

РЕДАКТИРОВАТЬ: Просто чтобы уточнить, я знаю, что приведенный ниже код неверен, это то, что я предполагал, происходит в моей программе. Я спрашивал, не вызовет ли это проблему - и это действительно так. :-)

РЕДАКТИРОВАТЬ: Похоже, что есть дополнительный случай, который позволяет вам выбрать сущность, прежде чем она будет стерта. Итак, похоже, что прогресс кода выглядит следующим образом:

selectedEntity = obj;
NSAutoreleasePool *pool = ...;
[obj release];
if (selectedEntity != nil && etc...) {}
[pool release];

Итак, я предполагаю, что, поскольку пул Autorelease еще не выпущен, объект не равен нулю, но его счетчик сохранения равен 0, поэтому к нему все равно нельзя получить доступ... или что-то в этом роде?

Кроме того, моя игра однопоточная, так что это не проблема с потоками.

EDIT: я решил проблему двумя способами. Во-первых, я не разрешил выбор объекта в этом второстепенном случае. Во-вторых, вместо простого вызова [entities removeObjectAtIndex:i] (код для удаления любых объектов, которые будут удалены), я изменил его на:

//Deselect it if it has been selected.
if (entity == selectedEntity)
{
    selectedEntity = nil;
}

[entities removeObjectAtIndex:i];

Просто убедитесь, что вы присваиваете переменной nil одновременно с ее освобождением, как предлагает jib.


person Eli    schedule 31.08.2009    source источник
comment
-removeObjectAtIndex: уменьшит счетчик сохранения и, если он достигнет нуля, освободит объект. Зомби обнаружат это. Таким образом, ваше присвоение nil в последнем редактировании устраняет проблему.   -  person bbum    schedule 01.09.2009


Ответы (5)


если объект (selectedEntity) был освобожден и освобожден, он не == nil. Это указатель на произвольный участок памяти, и его отнесение ( if(selectedEntity!=nil ) является ошибкой программирования (EXC_BAD_ACCESS).

Отсюда общая парадигма obj-c:

[релиз selectedEntity]; выбранный объект = ноль;

person Community    schedule 31.08.2009
comment
Это как раз то, что мне нужно было знать. Большое спасибо! - person Eli; 01.09.2009
comment
Это неправильно. selectedEntity != nil никогда не вызовет EXC_BAD_ACCESS. EXC_BAD_ACCESS произойдет только при разыменовании указателя, что произойдет, когда объект будет отправлен. - person bbum; 01.09.2009
comment
Да, правда, вы всегда можете проверить значение чего-либо, не вызвав ошибки, вы просто не можете сообщить об этом. - person Eli; 01.09.2009

К короткому замыканию это отношения не имеет. Objective-C потребляет сообщения до нуля, поэтому проверка на selectedEntity != nil не требуется (поскольку сообщения до нуля будут возвращать NO для типов возврата BOOL).

EXC_BAD_ACCESS не является перехватываемым исключением. Это катастрофический сбой, обычно вызванный попыткой следовать недопустимому указателю.

Скорее всего, любой объект, на который указывает selectedEntity, был освобожден до выполнения кода. Таким образом, это ни нуль, ни действительный объект.

Включите NSZombies и повторите попытку.

Если ваше приложение является многопоточным, правильно ли вы синхронизируете selectedEntity между потоками (имея в виду, что, как правило, использование пользовательского интерфейса из вторичных потоков не поддерживается)?


Ваше сообщение было отредактировано, чтобы указать, что исправление:

//Deselect it if it has been selected.
if (entity == selectedEntity)
{
    selectedEntity = nil;
}

[entities removeObjectAtIndex:i];

Это устраняет проблему, поскольку NSMutableArray освобождает объекты при удалении. Если счетчик сохранения падает до нуля, объект освобождается, и selectedEntity будет указывать на освобожденный объект.

person bbum    schedule 31.08.2009
comment
Вы указали мне правильное направление, я думаю. Вы видите, имеет ли смысл мое редактирование для вас? - person Eli; 31.08.2009

я только что прочитал это http://developer.apple.com/mac/library/qa/qa2004/qa1367.html, в котором указывалось, что ошибка, которую вы получаете, является результатом чрезмерного выпуска объекта. это означает, что, несмотря на то, что selectedEntity равен нулю, вы выпускали его много раз, и вы больше не можете его использовать.

person Nir Levy    schedule 31.08.2009
comment
Это не всегда проблема чрезмерного выпуска, и не в этом случае (я никогда не вызываю выпуск или сохранение для объекта, он просто добавляется, а затем удаляется из NSMutableArray). - person Eli; 01.09.2009

Поставьте точку останова на OBJC_EXCEPTION_THROW и посмотрите, где он действительно выбрасывается. Эта строка никогда не должна выдавать EXC_BAD_ACCESS сама по себе.

Возможно, вы делаете что-то в блоке IF, что может вызвать исключение?

person Marcus S. Zarra    schedule 31.08.2009

 

selectedEntity = obj;
NSAutoreleasePool *pool = ...;
[obj release];
if (selectedEntity != nil && etc...) {}
[pool release];

 

У вас здесь болтается указатель или зомби. selectedEntity указывает на obj, который получает релизы прямо перед тем, как вы ссылаетесь на selectedEntity. Это делает selectedEntity не нулевым, а недопустимым объектом, поэтому любое его разыменование приведет к сбою.

Вы хотели автоматически выпускать объект, а не выпускать его.

person Russell Zornes    schedule 31.08.2009
comment
Я должен был быть более ясным - я знаю, что приведенный выше код является проблемой, это очень упрощенная версия того, что, как я подозревал, происходит. Я не спрашивал, правильный ли это код. :-) - person Eli; 01.09.2009