Objective-C使用@try @catch @finally來捕獲並處理異常。處理異常須要用到NSException類,它是全部異常的基類。你能夠直接使用NSException類來捕獲異常,也能夠繼承一個新的類。app
Objective-C是C語言的擴充,它的異常處理機制是經過C標準庫提供兩個特殊的函數setjmp()和longjmp()函數實現的。若是對C的異常處理機制和setjmp、longjmp函數不瞭解的,建議先閱讀:C語言異常處理機制。編輯器
先來看看下面的例子:ide
#import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { @autoreleasepool { @try { NSException *e = [NSException exceptionWithName:@"FileNotFoundException" reason:@"File Not Found on System" userInfo:nil]; @throw e; } @catch (NSException *exception) { if ([[exception name] isEqualToString:NSInvalidArgumentException]) { NSLog(@"%@", exception); } else { @throw exception; } } @finally { NSLog(@"finally"); } } return 0; }
例子很簡單,在@try中拋出一個自定義的FileNotFoundException類型的異常,而後在@catch中判斷捕獲的異常是否是NSInvalidArgumentException類型,若是不是,將異常再次拋出。最後老是會執行@finally語句,通常異常處理的善後工做都放這裏來作。函數
如何才能瞭解它內部的工做流程,@try @catch @finally的定義沒法查看。幸運的是咱們能夠經過Clang生成C的中間代碼來了解try/catch原理。想了解Clang推薦閱讀:編譯器Clang介紹。ui
以上面的代碼爲例,使用文本編輯器將代碼保存到main.m文件中,文件名可隨便定義。打開終端輸入:clang -rewrite-objc main.m 命令編譯。this
獲得一份main.cpp文件:spa
struct objc_selector; struct objc_class; struct __rw_objc_super { struct objc_object *object; struct objc_object *superClass; }; #ifndef _REWRITER_typedef_Protocol typedef struct objc_object Protocol; #define _REWRITER_typedef_Protocol #endif #define __OBJC_RW_DLLIMPORT extern __OBJC_RW_DLLIMPORT struct objc_object *objc_msgSend(struct objc_object *, struct objc_selector *, ...); __OBJC_RW_DLLIMPORT struct objc_object *objc_msgSendSuper(struct objc_super *, struct objc_selector *, ...); __OBJC_RW_DLLIMPORT struct objc_object *objc_msgSend_stret(struct objc_object *, struct objc_selector *, ...); __OBJC_RW_DLLIMPORT struct objc_object *objc_msgSendSuper_stret(struct objc_super *, struct objc_selector *, ...); __OBJC_RW_DLLIMPORT double objc_msgSend_fpret(struct objc_object *, struct objc_selector *, ...); __OBJC_RW_DLLIMPORT struct objc_object *objc_getClass(const char *); __OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *); __OBJC_RW_DLLIMPORT struct objc_object *objc_getMetaClass(const char *); __OBJC_RW_DLLIMPORT void objc_exception_throw(struct objc_object *); __OBJC_RW_DLLIMPORT void objc_exception_try_enter(void *); __OBJC_RW_DLLIMPORT void objc_exception_try_exit(void *); __OBJC_RW_DLLIMPORT struct objc_object *objc_exception_extract(void *); __OBJC_RW_DLLIMPORT int objc_exception_match(struct objc_class *, struct objc_object *); __OBJC_RW_DLLIMPORT void objc_sync_enter(struct objc_object *); __OBJC_RW_DLLIMPORT void objc_sync_exit(struct objc_object *); __OBJC_RW_DLLIMPORT Protocol *objc_getProtocol(const char *); #ifndef __FASTENUMERATIONSTATE struct __objcFastEnumerationState { unsigned long state; void **itemsPtr; unsigned long *mutationsPtr; unsigned long extra[5]; }; __OBJC_RW_DLLIMPORT void objc_enumerationMutation(struct objc_object *); #define __FASTENUMERATIONSTATE #endif #ifndef __NSCONSTANTSTRINGIMPL struct __NSConstantStringImpl { int *isa; int flags; char *str; long length; }; #ifdef CF_EXPORT_CONSTANT_STRING extern "C" __declspec(dllexport) int __CFConstantStringClassReference[]; #else __OBJC_RW_DLLIMPORT int __CFConstantStringClassReference[]; #endif #define __NSCONSTANTSTRINGIMPL #endif #ifndef BLOCK_IMPL #define BLOCK_IMPL struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; // Runtime copy/destroy helper functions (from Block_private.h) #ifdef __OBJC_EXPORT_BLOCKS extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int); extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int); extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32]; extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32]; #else __OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int); __OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int); __OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32]; __OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32]; #endif #endif #define __block #define __weak #define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER) static __NSConstantStringImpl __NSConstantStringImpl_main_m_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"FileNotFoundException",21}; static __NSConstantStringImpl __NSConstantStringImpl_main_m_1 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"File Not Found on System",24}; static __NSConstantStringImpl __NSConstantStringImpl_main_m_2 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"%@",2}; static __NSConstantStringImpl __NSConstantStringImpl_main_m_3 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"finally",7}; // // main.c // TestBlock // // Created by xxxx on 13-6-2. // Copyright (c) 2013 xxxx. All rights reserved. // #include <Foundation/Foundation.h> int main (int argc, const char * argv[]) { @autoreleasepool { /* @try scope begin */ { struct _objc_exception_data { int buf[18/*32-bit i386*/]; char *pointers[4]; } _stack; id volatile _rethrow = 0; objc_exception_try_enter(&_stack); if (!_setjmp(_stack.buf)) /* @try block continue */ { NSException *e = ((NSException *(*)(id, SEL, NSString *, NSString *, NSDictionary *))(void *)objc_msgSend)(objc_getClass("NSException"), sel_registerName("exceptionWithName:reason:userInfo:"), (NSString *)&__NSConstantStringImpl_main_m_0, (NSString *)&__NSConstantStringImpl_main_m_1, (NSDictionary *)((void *)0)); objc_exception_throw(e); } /* @catch begin */ else { id _caught = objc_exception_extract(&_stack); objc_exception_try_enter (&_stack); if (_setjmp(_stack.buf)) _rethrow = objc_exception_extract(&_stack); else { /* @catch continue */ if (objc_exception_match((struct objc_class *)objc_getClass("NSException"), (struct objc_object *)_caught)) { NSException *exception = _caught; if (((BOOL (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("name")), sel_registerName("isEqualToString:"), (NSString *)NSInvalidArgumentException)) { NSLog((NSString *)&__NSConstantStringImpl_main_m_2, exception); } else { objc_exception_throw( exception); } } /* last catch end */ else { _rethrow = _caught; objc_exception_try_exit(&_stack); } } /* @catch end */ } /* @finally */ { if (!_rethrow) objc_exception_try_exit(&_stack); NSLog((NSString *)&__NSConstantStringImpl_main_m_3); if (_rethrow) objc_exception_throw(_rethrow); } } /* @try scope end */ } return 0; }
文件中信息量太大,我們只看main函數部分,下面的代碼把main函數的代碼做了註釋說明:.net
#include <Foundation/Foundation.h> int main (int argc, const char * argv[]) { @autoreleasepool { /** * try/catch的做用域從這裏開始 */ /* @try scope begin */ { /** * 首先定義一個_objc_exception_data類型的結構體,用來保存異常現場的數據。 */ struct _objc_exception_data { /** * buf變量就是c語言中的jmp_buf * jmp_buf的定義可在setjmp.h文件中找到: * * #define _JBLEN (10 + 16 + 2) * #define _JBLEN_MAX _JBLEN * * typedef int jmp_buf[_JBLEN]; */ int buf[18/*32-bit i386*/]; /** * pointers[0]用來存儲經過@throw拋出的異常對象, * pointers[1]存儲下一個_stack數據。 */ char *pointers[4]; } _stack; /** * _rethrow保存可能在@catch中再次拋出的異常對象。 */ id volatile _rethrow = 0; /** * 由於異常處理支持嵌套,_stack會被存儲在一個全局的棧中,這個棧用單鏈表的存儲結構表示。 * objc_exception_try_enter函數將_stack壓棧。 */ objc_exception_try_enter(&_stack); /** * _setjmp是C的函數,用於保存當前程序現場。 * _setjmp須要傳入一個jmp_buf參數,保存當前須要用到的寄存器的值。 * _setjmp()它能返回兩次,第一次是初始化時,返回0,第二次遇到_longjmp()函數調用會返回,返回值由_longjmp的第二個參數決定。 * 若是對_setjmp()和_longjmp()概念不太瞭解的,請參考C語言的異常處理機制。 * * 下面_setjmp()初始化返回0,而後執行if{}中也就是@try{}中的代碼。 */ if (!_setjmp(_stack.buf)) /* @try block continue */ { /** * 建立一個NSException對象,對應代碼: * * NSException *e = [NSException * exceptionWithName:@"FileNotFoundException" * reason:@"File Not Found on System" * userInfo:nil]; */ NSException *e = ((NSException *(*)(id, SEL, NSString *, NSString *, NSDictionary *))(void *)objc_msgSend)(objc_getClass("NSException"), sel_registerName("exceptionWithName:reason:userInfo:"), (NSString *)&__NSConstantStringImpl_main_m_0, (NSString *)&__NSConstantStringImpl_main_m_1, (NSDictionary *)((void *)0)); /** * 拋出異常對象,對應代碼:@throw e; * * objc_exception_throw函數實現步驟以下: * 1. 把e對象保存到_stack->pointers[0]中使其在@catch{}中能被捕獲。 * 2. 將_stack從全局棧中彈出。 * 3. 調用_longjmp()跳轉到前面if語句中的_setjmp()位置。_longjmp()使得_setjmp()函數第二次返回, * 返回值爲1,因此會執行else{}中也就是@catch{}中的代碼。 */ objc_exception_throw(e); } /* @catch begin */ else { /** * objc_exception_extract函數從_stack->pointers[0]中取得上面拋出的異常對象。 */ id _caught = objc_exception_extract(&_stack); /** * 這裏爲什麼再次調用objc_exception_try_enter對_stack壓棧?先保留這個疑問,繼續看下面的代碼。 */ objc_exception_try_enter (&_stack); /** * 在@catch中設置一個跳轉位置 */ if (_setjmp(_stack.buf)) /** * 若是@catch{}中再次拋出異常,在這裏捕獲。 */ _rethrow = objc_exception_extract(&_stack); else { /* @catch continue */ /** * objc_exception_match函數判斷_caught對象是不是須要捕獲的目標對象。對應代碼: * * @catch (NSException *exception) { */ if (objc_exception_match((struct objc_class *)objc_getClass("NSException"), (struct objc_object *)_caught)) { NSException *exception = _caught; /** * 比較捕獲的異常是否是NSInvalidArgumentException類型。對應代碼: * * if ([[exception name] isEqualToString:NSInvalidArgumentException]) { * NSLog(@"%@", exception); * */ if (((BOOL (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("name")), sel_registerName("isEqualToString:"), (NSString *)NSInvalidArgumentException)) { NSLog((NSString *)&__NSConstantStringImpl_main_m_2, exception); } else { /** * 拋出異常對象,而後跳轉到前面@catch中的if語句中的_setjmp()位置。 * 這就解釋了前面爲何要在@catch中再次將_stack壓棧和調用_setjmp()的緣由。 * 在當前@catch中,若是不設置一個跳轉點來捕獲@catch中拋出的異常,那麼程序就直接跳轉到全局棧的下一個@catch中,而下面的@finally{}代碼就沒法執行。 * 在@catch中設置跳轉點就是爲了最後總能執行@finally中的代碼。 */ objc_exception_throw( exception); } } /* last catch end */ else { /** * 若是異常對象沒被處理,先將其保存到_rethrow變量。 * objc_exception_try_exit函數將_stack從全局棧中彈出。 */ _rethrow = _caught; objc_exception_try_exit(&_stack); } } /* @catch end */ } /* @finally */ { if (!_rethrow) objc_exception_try_exit(&_stack); NSLog((NSString *)&__NSConstantStringImpl_main_m_3); /** * _rethrow是前面@catch中沒有被處理的或被捕獲的異常對象, * 最後,_rethrow異常對象被拋到全局棧的下一個@catch中。 */ if (_rethrow) objc_exception_throw(_rethrow); } } /* @try scope end */ } return 0; }
以上代碼中還涉及了objc_exception_try_enter、objc_exception_extract、objc_exception_throw、objc_exception_try_exit等函數,這些函數能夠在蘋果開源的objc4的objc-exception.mm文件中找到,objc4源碼可在這裏下載。下面代碼只顯示部分方便閱讀:code
typedef struct { int version; void (*throw_exc)(id); // version 0 void (*try_enter)(void *); // version 0 void (*try_exit)(void *); // version 0 id (*extract)(void *); // version 0 int (*match)(Class, id); // version 0 } objc_exception_functions_t; static objc_exception_functions_t xtab; // forward declaration static void set_default_handlers(); /* * Exported functions */ // get table; version tells how many void objc_exception_get_functions(objc_exception_functions_t *table) { // only version 0 supported at this point if (table && table->version == 0) *table = xtab; } // set table void objc_exception_set_functions(objc_exception_functions_t *table) { // only version 0 supported at this point if (table && table->version == 0) xtab = *table; } /* * The following functions are * synthesized by the compiler upon encountering language constructs */ void objc_exception_throw(id exception) { if (!xtab.throw_exc) { set_default_handlers(); } if (PrintExceptionThrow) { _objc_inform("EXCEPTIONS: throwing %p (%s)", exception, object_getClassName(exception)); void* callstack[500]; int frameCount = backtrace(callstack, 500); backtrace_symbols_fd(callstack, frameCount, fileno(stderr)); } OBJC_RUNTIME_OBJC_EXCEPTION_THROW(exception); // dtrace probe to log throw activity. xtab.throw_exc(exception); _objc_fatal("objc_exception_throw failed"); } void objc_exception_try_enter(void *localExceptionData) { if (!xtab.throw_exc) { set_default_handlers(); } xtab.try_enter(localExceptionData); } void objc_exception_try_exit(void *localExceptionData) { if (!xtab.throw_exc) { set_default_handlers(); } xtab.try_exit(localExceptionData); } id objc_exception_extract(void *localExceptionData) { if (!xtab.throw_exc) { set_default_handlers(); } return xtab.extract(localExceptionData); } int objc_exception_match(Class exceptionClass, id exception) { if (!xtab.throw_exc) { set_default_handlers(); } return xtab.match(exceptionClass, exception); } // quick and dirty exception handling code // default implementation - mostly a toy for use outside/before Foundation // provides its implementation // Perhaps the default implementation should just complain loudly and quit extern void _objc_inform(const char *fmt, ...); typedef struct { jmp_buf buf; void *pointers[4]; } LocalData_t; typedef struct _threadChain { LocalData_t *topHandler; objc_thread_t perThreadID; struct _threadChain *next; } ThreadChainLink_t; static ThreadChainLink_t ThreadChainLink; static ThreadChainLink_t *getChainLink() { // follow links until thread_self() found (someday) XXX objc_thread_t self = thread_self(); ThreadChainLink_t *walker = &ThreadChainLink; while (walker->perThreadID != self) { if (walker->next != NULL) { walker = walker->next; continue; } // create a new one // XXX not thread safe (!) // XXX Also, we don't register to deallocate on thread death walker->next = (ThreadChainLink_t *)malloc(sizeof(ThreadChainLink_t)); walker = walker->next; walker->next = NULL; walker->topHandler = NULL; walker->perThreadID = self; } return walker; } static void default_try_enter(void *localExceptionData) { LocalData_t *data = (LocalData_t *)localExceptionData; ThreadChainLink_t *chainLink = getChainLink(); data->pointers[1] = chainLink->topHandler; chainLink->topHandler = data; if (PrintExceptions) _objc_inform("EXCEPTIONS: entered try block %p\n", chainLink->topHandler); } static void default_throw(id value) { ThreadChainLink_t *chainLink = getChainLink(); LocalData_t *led; if (value == nil) { if (PrintExceptions) _objc_inform("EXCEPTIONS: objc_exception_throw with nil value\n"); return; } if (chainLink == NULL) { if (PrintExceptions) _objc_inform("EXCEPTIONS: No handler in place!\n"); return; } if (PrintExceptions) _objc_inform("EXCEPTIONS: exception thrown, going to handler block %p\n", chainLink->topHandler); led = chainLink->topHandler; chainLink->topHandler = (LocalData_t *) led->pointers[1]; // pop top handler led->pointers[0] = value; // store exception that is thrown #if TARGET_OS_WIN32 longjmp(led->buf, 1); #else _longjmp(led->buf, 1); #endif } static void default_try_exit(void *led) { ThreadChainLink_t *chainLink = getChainLink(); if (!chainLink || led != chainLink->topHandler) { if (PrintExceptions) _objc_inform("EXCEPTIONS: *** mismatched try block exit handlers\n"); return; } if (PrintExceptions) _objc_inform("EXCEPTIONS: removing try block handler %p\n", chainLink->topHandler); chainLink->topHandler = (LocalData_t *) chainLink->topHandler->pointers[1]; // pop top handler } static id default_extract(void *localExceptionData) { LocalData_t *led = (LocalData_t *)localExceptionData; return (id)led->pointers[0]; } static int default_match(Class exceptionClass, id exception) { //return [exception isKindOfClass:exceptionClass]; Class cls; for (cls = _object_getClass(exception); nil != cls; cls = _class_getSuperclass(cls)) if (cls == exceptionClass) return 1; return 0; } static void set_default_handlers() { objc_exception_functions_t default_functions = { 0, default_throw, default_try_enter, default_try_exit, default_extract, default_match }; // should this always print? if (PrintExceptions) _objc_inform("EXCEPTIONS: *** Setting default (non-Foundation) exception mechanism\n"); objc_exception_set_functions(&default_functions); } void exception_init(void) { // nothing to do } void _destroyAltHandlerList(struct alt_handler_list *list) { // nothing to do } // !__OBJC2__ #else // __OBJC2__