Как реализовать глобальную обработку исключений iPhone?

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

Какой умный способ справиться с проблемой такого рода?

6 ответов

Решение

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

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

Когда в вашем приложении возникает исключение, оно обрабатывается обработчиком исключений по умолчанию. Этот обработчик делает не что иное, как запись сообщения на консоль перед закрытием приложения. Вы можете переопределить это, установив свой собственный обработчик исключений, используя функцию, указанную выше. Лучшее место для этого - делегат приложения applicationDidFinishLaunching: метод.

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    NSSetUncaughtExceptionHandler(&myExceptionHandler);
}

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

void myExceptionHandler(NSException *exception)
{
    NSArray *stack = [exception callStackReturnAddresses];
    NSLog(@"Stack trace: %@", stack);
}

К сожалению, по сравнению с OSX iPhone выглядит довольно ограниченным в плане создания хорошего следа стека. Приведенный выше код выдаст на первый взгляд ненужный вывод; однако вы можете запустить этот вывод через инструмент atos, и вы сможете сгенерировать из него полезную трассировку стека.

Другой вариант - следовать инструкциям, приведенным в этой статье, которые помогут автоматически создать хороший след стека.

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

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

https://developer.apple.com/library/content/technotes/tn2151/_index.html

ОБНОВЛЕНИЕ: Хотя этот пост по-прежнему содержит полезную информацию, некоторые из ссылок, которые он содержит, необратимо мертвы. Рекомендуется использовать информацию из этого альтернативного поста.

Если вы планируете сделать это самостоятельно, вы можете использовать один из этих подходов

Approach1:

void onUncaughtException(NSException* exception)
{
//save exception details
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSSetUncaughtExceptionHandler(&onUncaughtException);
  //Add coding to find if any exception has occurred from saved details if so send it to server or ask user to comment on the issue.
//Rest of the coding
}

Approach2:

void onUncaughtException(NSException* exception)
{

//Save exception details

}

int main(int argc, char *argv[])
{
    @autoreleasepool {

        NSSetUncaughtExceptionHandler(&onUncaughtException);

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([SGGI_AppDelegate class]));
    }
}



-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
      //Add coding to find if any exception has occurred from saved details if so send it to server or ask user to comment on the issue.
    //Rest of the coding
 }

Approcach3:

int main(int argc, char *argv[])
{
    @autoreleasepool {

        @try {

            return UIApplicationMain(argc, argv, nil, NSStringFromClass([SGGI_AppDelegate class]));
        }
        @catch (NSException *exception) {      
        //Save the exception
        }
        @finally {
        }

    }
}

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
      //Add coding to find if any exception has occurred from saved details if so send it to server or ask user to comment on the issue.
    //Rest of the coding
 }

Замечания:

  • С моей точки зрения, не пытайтесь отправить информацию об исключении на сервер в момент сбоя, отправьте его, когда он снова запустит приложение.

  • Если вы собираетесь использовать NSUserDefaults для сохранения сведений об исключении, то вам придется синхронизировать его во время сбоя, иначе оно не будет сохраняться.

Следующий фрагмент кода делает эту работу.

   - (void)applicationWillTerminate:(UIApplication *)application
   {
       [[NSUserDefaults standardUserDefaults]synchronize];
   }
  • Если вы предпочитаете сохранить его на sqlite db, тогда он сохраняется, и нет необходимости вызывать что-либо для сохранения во время сбоя.

В XCode вы всегда должны устанавливать глобальную точку останова для objc_exception_throw, Тогда вы (обычно) получаете намного более значимый след стека относительно того, что на самом деле пытается вызвать исключение.

Вы все еще можете получить исключения, которые происходят из кода таймера или других мест без собственного кода где-либо в трассировке, но если вы посмотрите на цепочку методов, вы обычно можете выяснить, в общем, что такое исключение (например, отправив уведомление, где цель ушел).

Еще одна опция для отслеживания отчетов о сбоях - это Plausible CrashReporter, открытый исходный код для автоматической отправки вам отчетов о сбоях с поля.

Также есть CrashReporterDemo, еще одна опция с открытым исходным кодом, которая представляет собой комбинацию Plausible CrashReporter и некоторого серверного кода для лучшего отслеживания отчетов о сбоях.

И, наконец, есть MacDevCrashReporter, сервис, который, похоже, имеет сходство с iOSExceptional.com, предлагается в другом ответе. Я понятия не имею, каковы их условия обслуживания, поскольку я не подписался на бета-версию. Определенно стоит проверить, прежде чем войти слишком глубоко.

Ты пытался NSSetUncaughtExceptionHandler?

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

Вы также можете загрузить DYSM для вашей конкретной сборки, и он автоматически символизирует сбой для вас на их веб-сайте. Это должно предоставить вам наиболее четкую трассировку стека, не будучи подключенным к отладчику.

Вы также можете убедиться, что вы настроены на исключение Objetive-C. В Xcode 4 на вкладке точек останова вы можете добавить Исключение точки останова, которое разбивается как на исключения C / C, так и Obj-C. Без этого большинство трассировок стека для создаваемых исключений довольно бесполезны.

Удачи!

Другие вопросы по тегам