Почему этот вызов Objective C зависает?

Мой друг обнаружил какое-то странное поведение с NSDictionary, и мне любопытно, почему это происходит. Рассмотрим следующий код:

NSDictionary *dict = [[NSDictionary alloc] init];

// Oops, we can't mutate an NSDictionary
[dict setObject:[[NSNull alloc] init] forKey:@"test"];
NSLog(@"Set");

При компиляции код выдает предупреждение о том, что «NSDictionary может не отвечать на setObject: forKey:». Это все хорошо, и если вы все равно запустите его, вы получите этот вывод в консоли:

-[__NSCFDictionary setObject:forKey:]: мутирующий метод отправляется в неизменяемый объект

Опять же, именно то, что вы ожидаете. Однако в этот момент приложение не аварийно завершает работу из-за неперехваченного исключения. Метод setObject:forKey: просто никогда не возвращается, и приложение зависает; следующий NSLog никогда не выполняется. Если вы попытаетесь перейти или войти в метод с помощью GDB, отладка просто закончится, но без какого-либо явного сообщения об ошибке. Приложение продолжает работать, но отладчик не дает сведений о том, где в коде выполнение «застряло».

Что тут происходит? Что на самом деле делает приложение в этом случае и почему оно не падает с NSInternalInconsistencyException или чем-то подобным?

Редактировать: для тех, кто спрашивал, я запускаю XCode 4.1 на OS X Lion (10.7.2) с помощью «компилятора Apple LLVM 2.1». Я использую все настройки по умолчанию, которые вы получаете с новым проектом Cocoa в XCode 4. Я испытываю одно и то же поведение без сбоев независимо от того, отлаживаю ли я программу или просто «запускаю» ее. Переход от сборки Debug к сборке Release не имеет значения. Я даже могу найти файл .app вручную в Finder и дважды щелкнуть по нему, чтобы выполнить его вне XCode, и он все равно не падает.


person Mitch Lindgren    schedule 13.12.2011    source источник
comment
Попробуйте запустить его не под отладчиком; Бьюсь об заклад, он сильно падает. Я считаю, что среда отладчика может оказывать неожиданное влияние на среду выполнения, особенно в отношении исключений и того, что происходит, когда вы сталкиваетесь с ними.   -  person Ben Zotto    schedule 14.12.2011
comment
Когда возникает исключение, управление возобновляется с тем, что перехватывает исключение, а не со следующей строки после исключения. В вашем примере либо отладчик, либо недра рантайма поймали исключение. В любом случае вы не ожидаете, что NSLog произойдет после того, как -setObject:forKey: выдало исключение.   -  person Vincent Gable    schedule 14.12.2011
comment
@VincentGable: Согласен, если программа выйдет из строя, я ничуть не удивлюсь, что NSLog не был вызван. Что меня удивляет, так это то, что он не падает и не переходит на следующую строку. Кажется, что это должно сделать одно или другое. Для тех, кто спрашивал, я обновил вопрос, добавив некоторые дополнительные сведения о среде, в которой я работаю.   -  person Mitch Lindgren    schedule 14.12.2011
comment
В значительной степени дубликат stackoverflow.com/q/3336278/341994   -  person matt    schedule 16.12.2011


Ответы (1)


Исключения не приводят к сбою программ AppKit. NSApplication устанавливает обработчик исключений по умолчанию, который перехватывает исключения, которых нет в вашем коде. Затем вы просто возвращаетесь в цикл выполнения, как обычно.

Многие приложения демонстрируют такое поведение. Это частая причина необъяснимых пустых представлений/окон. Если исключение произойдет до того, как представление завершит рисование, оно будет пустым, но приложение не выйдет из строя. Исключения вызывают сбой только в том случае, если вы намеренно изменили обработчик исключений по умолчанию на сбой.

person Bored Astronaut    schedule 15.12.2011
comment
Спасибо, это в основном отвечает на мой вопрос. Однако я все еще не понимаю, почему я не вижу, чтобы приложение возвращалось в основной цикл событий после того, как было перехвачено исключение. Или это происходит в другой теме? Я не очень хорошо знаком с внутренней работой AppKit. - person Mitch Lindgren; 18.12.2011