Приложение iOS падает при возобновлении

(СМОТРИТЕ ОБНОВЛЕНИЕ НА НОМЕРЕ)

Недавно я начал получать странный и редкий крах моего iPhone-приложения, когда он возвращается с фона. Журнал сбоев состоит только из системных вызовов:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000138
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x34c715b0 objc_msgSend + 16
1   CoreFoundation                  0x368b7034 _CFXNotificationPost + 1424
2   Foundation                      0x34379d8c -[NSNotificationCenter postNotificationName:object:userInfo:] + 68
3   UIKit                           0x37ddfec2 -[UIApplication _handleApplicationResumeEvent:] + 1290
4   UIKit                           0x37c37d5c -[UIApplication handleEvent:withNewEvent:] + 1288
5   UIKit                           0x37c376d0 -[UIApplication sendEvent:] + 68
6   UIKit                           0x37c3711e _UIApplicationHandleEvent + 6150
7   GraphicsServices                0x36dea5a0 _PurpleEventCallback + 588
8   CoreFoundation                  0x3693b680 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 12
9   CoreFoundation                  0x3693aee4 __CFRunLoopDoSources0 + 208
10  CoreFoundation                  0x36939cb2 __CFRunLoopRun + 642
11  CoreFoundation                  0x368aceb8 CFRunLoopRunSpecific + 352
12  CoreFoundation                  0x368acd44 CFRunLoopRunInMode + 100
13  GraphicsServices                0x36de92e6 GSEventRunModal + 70
14  UIKit                           0x37c8b2fc UIApplicationMain + 1116
15  [MyAppName]                     0x00083d60 main (main.m:20)
16  [MyAppName]                     0x00080304 start + 36

Это может выглядеть как объект-зомби, вызываемый на UIApplicationWillEnterForegroundNotification или UIApplicationDidBecomeActiveNotification (угадывание _handleApplicationResumeEvent в трассировке стека и время его сбоя), но:

  • Ни один из моих классов не регистрируется для UIApplicationDidBecomeActiveNotification, и только пара синглетов (которые остаются в живых навсегда) регистрируются для UIApplicationWillEnterForegroundNotification;
  • Я экспериментировал, и получается, что публикация UIApplicationWillEnterForegroundNotification идет от [UIApplication _sendWillEnterForegroundCallbacks:], и она не находится в журнале сбоев.

Для меня все это означает ошибку в некоторой библиотеке, которую я использую, или системную ошибку, и авария произошла один раз на iOS 5.1.1 (выпускная сборка), один раз на iOS 6.0 (выпускная сборка) и один раз на iOS 6.0 (сборка отладки). Я просмотрел каждую библиотеку, которую я использую, и имею доступ к исходному коду, и они не регистрируются ни для UIApplicationWillEnterForegroundNotification, ни UIApplicationDidBecomeActiveNotification. Единственной библиотекой, к которой у меня нет доступа, является TestFlight, но авария произошла и в версиях TestFlight, и в версии 1.0, и 1.1, и я уже давно использую это уже сейчас, без таких проблем. Итак, подводя итог, я понятия не имею, почему возник этот крах и что он исходит. Любые идеи?

ОБНОВЛЕНИЕ 1

Я исследовал проблему немного глубже, благодаря DarthMike и матовой для их помощи. Используя трассировку стека обратного вызова и ведения журнала уведомлений, я обнаружил, что этот точный стек возникает тогда и только тогда, когда уведомление UIApplicationResumedNotification запускается как часть возврата из фона. И угадайте, что это - это некоторое уведомление "private", и у него нет общего идентификатора. Он не имеет userInfo, а его объект UIApplication (как и многие другие уведомления, которые были опубликованы до этого). Очевидно, я не использую его, и у меня нет библиотеки, для которой есть исходный код. Я даже не могу найти разумного упоминания об этом в Интернете! Я также очень сомневаюсь, что TestFlight является виновником, потому что во время отладки произошел сбой, и я не "отключаю" TestFlight в режиме отладки.

Здесь трассировка стека для приема UIApplicationResumedNotification. Смещения одинаковы, но с постоянным смещением байта (2 или 4, в зависимости от библиотеки - возможно, потому что это трассировка стека отладки, а не выпуск):

0   [MyAppName]                         0x0016f509 NotificationsCallback + 72
1   CoreFoundation                      0x3598ce25 __CFNotificationCenterAddObserver_block_invoke_0 + 124
2   CoreFoundation                      0x35911037 _CFXNotificationPost + 1426
3   Foundation                          0x333d3d91 -[NSNotificationCenter postNotificationName:object:userInfo:] + 72
4   UIKit                               0x36e39ec7 -[UIApplication _handleApplicationResumeEvent:] + 1294
5   UIKit                               0x36c91d61 -[UIApplication handleEvent:withNewEvent:] + 1292
6   UIKit                               0x36c916d5 -[UIApplication sendEvent:] + 72
7   UIKit                               0x36c91123 _UIApplicationHandleEvent + 6154
8   GraphicsServices                    0x35e445a3 _PurpleEventCallback + 590
9   CoreFoundation                      0x35995683 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14
10  CoreFoundation                      0x35994ee9 __CFRunLoopDoSources0 + 212
11  CoreFoundation                      0x35993cb7 __CFRunLoopRun + 646
12  CoreFoundation                      0x35906ebd CFRunLoopRunSpecific + 356
13  CoreFoundation                      0x35906d49 CFRunLoopRunInMode + 104
14  GraphicsServices                    0x35e432eb GSEventRunModal + 74
15  UIKit                               0x36ce5301 UIApplicationMain + 1120
16  [MyAppName]                         0x000aa603 main + 390
17  [MyAppName]                         0x000a41b0 start + 40

NotificationsCallback - это обратный вызов "observer", который я добавил для отладки только сейчас.

Чтобы доказать точку, я сознательно пропустил вызов removeObserver: из одного из моих объектов, чтобы создать зомби/исключение, а трассировка стека по-прежнему включала _CFXNotificationPost + 1426, а затем сбой с EXC_BAD_ACCESS в objc_msgSend + 16, как и в моем первоначальном крахе. Таким образом, это означает, что кто-то зарегистрировал наблюдателя для UIApplicationResumedNotification и не удалил его до того, как наблюдатель был освобожден. Исходя из того факта, что я никогда не регистрировался для такого уведомления, могу предположить, что этот крах не является моей ошибкой. Тем не менее остается вопрос - чей это тогда? Интересно, кто на самом деле регистрируется для этого уведомления в любом случае...

ОБНОВЛЕНИЕ 2

Пока я все еще жду, чтобы увидеть, есть ли какие-либо изменения с этой ошибкой в ​​новой версии моего приложения, я получил еще один сбой в предыдущей версии, вызванной этим. Оказывается, что все регистры для UIApplicationResumedNotification задает для него селектор _applicationResuming:. Я сомневаюсь в том, что любой вариант использования.

Ответ 1

Я просто столкнулся с этой проблемой и нашел решение, которое не связано с удалением уведомлений. В нашем случае был старый код, который делал это:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
  [searchBar resignFirstResponder];
  // other stuff
}

Я не знаю, почему у нас это было, но теперь оно исчезло, и авария исчезла. Похоже, что в этом случае отставка первого ответчика, в то время как searchBarTextDidBeginEditing вызывает сироты, уведомление в поле редактирования текста в строке поиска, а затем мы сбой, как только диспетчер представлений, владеющий этим UISearchBar, был освобожден, и мы сделали background/foreground танец.

YMMV

Ответ 2

У меня была точно такая же трассировка стека в отчете о сбое с устройства, работающего на IOS 6.0.1. Мне удалось воспроизвести проблему на Simulator по следующей схеме:

  • Поместите приложение в фоновом режиме
  • Имитировать предупреждение о памяти из меню симулятора
  • Верните приложение на передний план

После большой отладки я обнаружил, что сообщение _applicationResuming: отправляется на UITextField, который я выпускаю в качестве реакции на Memory Warning. Я тестировал тот же шаблон в IOS 5.1, но это не вызвало краха. По какой-то причине в IOS 6 UITextField регистрируются для ApplicationResumeEvent (возможно, не всегда, но после появления клавиатуры).
Мое обходное решение состояло в том, чтобы удалить этот объект из NSNotificationCenter, прежде чем выпустить его:

[[NSNotificationCenter defaultCenter] removeObserver:self.placeFld];
self.placeFld = nil;

Ответ 3

Поместите точку останова на -[NSNotificationCenter postNotificationName:object:userInfo:]. Он пытается отправить уведомление объекту, которого больше нет, или что-то в этом роде. Возможно, вы неправильно понимаете свои собственные уведомления или свои собственные объекты.

Рассмотрите возможность переключения на ARC, если вы еще не используете его.

Используйте статический анализатор. Он может найти потенциальные проблемы с памятью.

Ответ 4

Может быть много вещей, но я думаю, что проверка кода, который регистрируется для ЛЮБЫХ уведомлений об использовании UIApplication, будет лучше. Вы действительно не знаете, какое уведомление вызывает ошибку.

Кроме того, есть ли какой-либо объект, сохраняющий/поддерживающий сильную ссылку на AppDelegate? Это может вызвать некоторый странный цикл сохранения, приводящий к этому сбою.

Я никогда не видел такого сбоя от неправильного поведения XCode.

EDIT: Вставка всех уведомлений из файла заголовка. может быть излишним, но некоторые могут быть отправлены в приложении резюме/из фона

UIKIT_EXTERN NSString *const UIApplicationDidEnterBackgroundNotification       NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationWillEnterForegroundNotification      NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationDidFinishLaunchingNotification;
UIKIT_EXTERN NSString *const UIApplicationDidBecomeActiveNotification;
UIKIT_EXTERN NSString *const UIApplicationWillResignActiveNotification;
UIKIT_EXTERN NSString *const UIApplicationDidReceiveMemoryWarningNotification;
UIKIT_EXTERN NSString *const UIApplicationWillTerminateNotification;
UIKIT_EXTERN NSString *const UIApplicationSignificantTimeChangeNotification;
UIKIT_EXTERN NSString *const UIApplicationWillChangeStatusBarOrientationNotification; // userInfo contains NSNumber with new orientation
UIKIT_EXTERN NSString *const UIApplicationDidChangeStatusBarOrientationNotification;  // userInfo contains NSNumber with old orientation
UIKIT_EXTERN NSString *const UIApplicationStatusBarOrientationUserInfoKey;            // userInfo dictionary key for status bar orientation
UIKIT_EXTERN NSString *const UIApplicationWillChangeStatusBarFrameNotification;       // userInfo contains NSValue with new frame
UIKIT_EXTERN NSString *const UIApplicationDidChangeStatusBarFrameNotification;        // userInfo contains NSValue with old frame
UIKIT_EXTERN NSString *const UIApplicationStatusBarFrameUserInfoKey;                  // userInfo dictionary key for status bar frame
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsURLKey                NS_AVAILABLE_IOS(3_0); // userInfo contains NSURL with launch URL
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsSourceApplicationKey  NS_AVAILABLE_IOS(3_0); // userInfo contains NSString with launch app bundle ID
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsRemoteNotificationKey NS_AVAILABLE_IOS(3_0); // userInfo contains NSDictionary with payload
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsLocalNotificationKey  NS_AVAILABLE_IOS(4_0); // userInfo contains a UILocalNotification
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsAnnotationKey         NS_AVAILABLE_IOS(3_2); // userInfo contains object with annotation property list
UIKIT_EXTERN NSString *const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationProtectedDataDidBecomeAvailable    NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsLocationKey           NS_AVAILABLE_IOS(4_0); // app was launched in response to a CoreLocation event.
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsNewsstandDownloadsKey NS_AVAILABLE_IOS(5_0); // userInfo contains an NSArray of NKAssetDownlo

Ответ 5

Несколько вещей, которые я делаю, чтобы очистить колоду, прежде чем отлаживать что-то странное сбой.

Build Clean (избавляется от локальных кешированных файлов).

Удалите приложение из Simulator/Device (иногда кешируются XIB).

Запустите Xcode (есть некоторые странные ошибки, когда Xcode не синхронизируется с текущей установкой).

Затем повторите попытку.