سؤال

المشكلة

أنا أكتب تطبيق Cocoa وأريد رفع الاستثناءات التي ستؤدي إلى تعطل التطبيق بشكل صاخب.

لدي الأسطر التالية في مندوب التطبيق الخاص بي:

[NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
abort();

المشكلة هي أنهم لا يقومون بإسقاط التطبيق - يتم تسجيل الرسالة فقط في وحدة التحكم ويستمر التطبيق في العمل بطريقة ممتعة.

كما أفهمها، المغزى الأساسي من الاستثناءات هو أنهم يتم فصلهم في ظل ظروف استثنائية.في هذه الظروف، أريد إنهاء الطلب بطريقة واضحة.وهذا لا يحدث.

ما حاولت

لقد حاولت:

-(void)applicationDidFinishLaunching:(NSNotification *)note
    // ...
    [self performSelectorOnMainThread:@selector(crash) withObject:nil waitUntilDone:YES];
}

-(void)crash {
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
    abort();
}

الذي لا يعمل و

-(void)applicationDidFinishLaunching:(NSNotification *)note
    // ...
    [self performSelectorInBackground:@selector(crash) withObject:nil];
}

-(void)crash {
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
    abort();
}

والذي، بشكل مربك إلى حد ما، يعمل كما هو متوقع.

ماذا يحدث هنا؟ما الخطأ الذي افعله؟

هل كانت مفيدة؟

المحلول

تحديث نوفمبر 16, 2010: هناك بعض القضايا مع هذا الجواب عندما يتم طرح استثناءات داخل IBAction الأساليب.انظر هذا الجواب بدلا من ذلك:

كيف يمكنني إيقاف HIToolbox من تمسك استثناءات ؟


هذا يوسع على ديفيد Gelhar هو الإجابة على الرابط قدم.أدناه هو كيف فعلت ذلك عن طريق تجاوز NSApplication هو -reportException: الأسلوب.أولا إنشاء ExceptionHandling فئة NSApplication (لمعلوماتك ، يجب إضافة 2-3 رسالة المختصر قبل "ExceptionHandling" للحد من مخاطر اسم تشتبك):

NSApplication+ExceptionHandling.ح

#import <Cocoa/Cocoa.h>

@interface NSApplication (ExceptionHandling)

- (void)reportException:(NSException *)anException;

@end

NSApplication+ExceptionHandling.م

#import "NSApplication+ExceptionHandling.h"

@implementation NSApplication (ExceptionHandling)

- (void)reportException:(NSException *)anException
{
    (*NSGetUncaughtExceptionHandler())(anException);
}

@end

الثانية, داخل NSApplication مندوب فعلت التالية:

AppDelegate.م

void exceptionHandler(NSException *anException)
{
    NSLog(@"%@", [anException reason]);
    NSLog(@"%@", [anException userInfo]);

    [NSApp terminate:nil];  // you can call exit() instead if desired
}

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    NSSetUncaughtExceptionHandler(&exceptionHandler);

    // additional code...

    // NOTE: See the "UPDATE" at the end of this post regarding a possible glitch here...
}

بدلا من استخدام NSApp هو terminate:, يمكنك الاتصال exit() بدلا من ذلك. terminate: هو أكثر الكاكاو-كوشير, على الرغم من أنك قد ترغب في تخطي الخاص بك applicationShouldTerminate: التعليمات البرمجية في الحدث استثناء ألقيت ببساطة من الصعب التصادم مع exit():

#import "sysexits.h"

// ...

exit(EX_SOFTWARE);

كلما تم طرح استثناء ، الترابط الرئيسي, و هو ليس أمسك و تدمير المخصصة الخاصة بك غير مسك استثناء معالج يطلق عليها حاليا بدلا من NSApplication هو.هذا يسمح لك أن تحطم التطبيق الخاص بك, من بين أمور أخرى.


تحديث:

يبدو أن هناك خلل صغير في التعليمات البرمجية أعلاه.مخصص استثناء معالج لن "ركلة" و العمل حتى بعد NSApplication الانتهاء من استدعاء كل من مندوب الأساليب.هذا يعني أنه إذا كان يمكنك القيام ببعض الإعداد رمز داخل applicationWillFinishLaunching: أو applicationDidFinishLaunching: أو awakeFromNib:, الافتراضي NSApplication استثناء معالج يبدو في اللعب حتى بعد تهيئة بشكل كامل.

ما يعنيه ذلك هو إذا كنت تفعل هذا:

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
        NSSetUncaughtExceptionHandler(&exceptionHandler);

        MyClass *myClass = [[MyClass alloc] init];   // throws an exception during init...
}

الخاص بك exceptionHandler لن تحصل على الاستثناء.NSApplication ، كما سجل ذلك.

لحل هذه المشكلة, ببساطة وضع أي رمز التهيئة داخل @try/@catch/@finally كتلة ويمكنك الاتصال الخاصة بك العرف exceptionHandler:

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    NSSetUncaughtExceptionHandler(&exceptionHandler);

    @try
    {
        MyClass *myClass = [[MyClass alloc] init];   // throws an exception during init...
    }
    @catch (NSException * e)
    {
        exceptionHandler(e);
    }
    @finally
    {
        // cleanup code...
    }
}

الآن exceptionHandler() يحصل على استثناء و يمكن التعامل معها وفقا لذلك.بعد NSApplication الانتهاء من استدعاء كل مندوب الأساليب ، NSApplication+ExceptionHandling.ح الفئة جزاء ، داعيا exceptionHandler() من خلال العرف -reportException: الأسلوب.في هذه المرحلة كنت لا داعي للقلق حول @حاول/@الصيد/@أخيرا عندما تريد الاستثناءات لرفع الخاص بك غير مسك استثناء معالج.

أنا قليلا في حيرة من ما يسبب هذا.ربما شيء من وراء الكواليس في API.ويحدث ذلك حتى عندما فرعية NSApplication ، بدلا من إضافة فئة.قد يكون هناك محاذير أخرى تعلق على هذا أيضا.

نصائح أخرى

هناك تبين أن حل بسيط جدا:

[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];

فإنه لا لا تحطم التطبيق الخاص بك إذا كنت تستخدم @try ... @catch.

لا أستطيع أن أتخيل لماذا هذا ليس الافتراضي.

لقد قمت بنشر هذا السؤال والإجابة كما أتمنى أن يخبرني شخص ما، أوه، منذ حوالي عام:

الاستثناءات التي يتم إلقاؤها على الخيط الرئيسي قد تم القبض عليها بواسطة NSApplication.

أنا Skim اقرأ المستندات على نهاية NSException إلى النهاية، دون ذكر ذلك أستطيع أن أذكر.السبب الوحيد الذي أعرفه أنه بسبب ديف الكاكاو الرائع:

p> http://www.cocoadev.com/index.pl؟exceptionhandling

الحل.أعتقد.

لدي برنامج مخفي بدون واجهة مستخدم يعمل بالكامل تقريبا على الخيط الرئيسي.سيتعين علي نقل التطبيق بأكمله لتشغيل مؤشرات الترابط الخلفية، ما لم يكن هناك شخص آخر يمكن أن يقترح وسيلة لإيقاف NSAPPlication اصطياد الاستثناءات التي أرميها.أنا متأكد من أن هذا غير ممكن.

أحاول فهم هذا بشكل صحيح: لماذا تؤدي طريقة الفئة التالية على NSAPPlication إلى حلقة لا حصر لها؟ في تلك الحلقة اللانهائية، تم تسجيل "استثناء غير مصراعي" يتم تسجيله بلا حدود عدة مرات:

giveacodicetagpre.

لاختبار (وأغراض فهم)، وهذا هو الشيء الوحيد الذي أقوم به، أي فقط إنشاء طريقة الفئة أعلاه. (وفقا للتعليمات في http://www.cocoadev.com/index.pl .stacktraces A>)

لماذا سيؤدي هذا إلى حلقة لانهائية؟ لا يتفق مع ما يجب أن تفعله طريقة معالج الاستثناء الافتراضي للاستثمار، أي فقط سجل الاستثناء والخروج من البرنامج. (انظر

لذلك اتضح أن السبب وراء عدم استدعاء معالج الاستثناء في أساليب تفويض التطبيق الخاص بك هو ذلك _NSAppleEventManagerGenericHandler (واجهة برمجة التطبيقات الخاصة) لديها @try @catch الكتلة التي تلتقط جميع الاستثناءات وتستدعي NSLog عليها قبل العودة بـ errAEEventNotHandled OSErr.هذا يعني أنك لن تفوت أي استثناءات في بدء تشغيل التطبيق فحسب، بل أيضًا أي استثناءات تحدث أثناء التعامل مع AppleEvent والتي تتضمن (على سبيل المثال لا الحصر) فتح المستندات والطباعة والخروج وأي AppleScript.

لذا، "الإصلاح" الخاص بي لهذا:

#import <Foundation/Foundation.h>
#include <objc/runtime.h>

@interface NSAppleEventManager (GTMExceptionHandler)
@end

@implementation NSAppleEventManager (GTMExceptionHandler)
+ (void)load {
  // Magic Keyword for turning on crashes on Exceptions
  [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];

  // Default AppleEventManager wraps all AppleEvent calls in a @try/@catch
  // block and just logs the exception. We replace the caller with a version
  // that calls through to the NSUncaughtExceptionHandler if set.
  NSAppleEventManager *mgr = [NSAppleEventManager sharedAppleEventManager];
  Class class = [mgr class];
  Method originalMethod = class_getInstanceMethod(class, @selector(dispatchRawAppleEvent:withRawReply:handlerRefCon:));
  Method swizzledMethod = class_getInstanceMethod(class, @selector(gtm_dispatchRawAppleEvent:withRawReply:handlerRefCon:));
  method_exchangeImplementations(originalMethod, swizzledMethod);
}

- (OSErr)gtm_dispatchRawAppleEvent:(const AppleEvent *)theAppleEvent
                      withRawReply:(AppleEvent *)theReply
                     handlerRefCon:(SRefCon)handlerRefCon {
  OSErr err;
  @try {
    err = [self gtm_dispatchRawAppleEvent:theAppleEvent withRawReply:theReply handlerRefCon:handlerRefCon];
  } @catch(NSException *exception) {
    NSUncaughtExceptionHandler *handler = NSGetUncaughtExceptionHandler();
    if (handler) {
      handler(exception);
    }
    @throw;
  }
  @catch(...) {
    @throw;
  }
  return err;
}
@end

ملاحظة إضافية ممتعة: NSLog(@"%@", exception) يعادل NSLog(@"%@", exception.reason). NSLog(@"%@", [exception debugDescription]) سيعطيك السبب بالإضافة إلى التتبع الخلفي للمكدس المرمز بالكامل.

الإصدار الافتراضي في _NSAppleEventManagerGenericHandler مكالمات فقط NSLog(@"%@", exception) (ماك 10.14.4 (18E226))

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top