Objective-C try/catch异常处理机制原理。
Posted zzfx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C try/catch异常处理机制原理。相关的知识,希望对你有一定的参考价值。
try-catch-finaly
finally在任何情况下都会执行(不管有没有异常),属于整个体系的附属。
基本思想是跳到捕获锚点,重新执行。
http://www.cnblogs.com/markhy/p/3169035.html
Objective-C使用@try @catch @finally来捕获并处理异常。处理异常需要用到NSException类,它是所有异常的基类。你可以直接使用NSException类来捕获异常,也可以继承一个新的类。
Objective-C是C语言的扩充,它的异常处理机制是通过C标准库提供两个特殊的函数setjmp()和longjmp()函数实现的。如果对C的异常处理机制和setjmp、longjmp函数不了解的,建议先阅读:C语言异常处理机制。
先来看看下面的例子:
#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介绍。
以上面的代码为例,使用文本编辑器将代码保存到main.m文件中,文件名可随便定义。打开终端输入:clang -rewrite-objc main.m 命令编译。
得到一份main.cpp文件:
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函数的代码作了注释说明:
#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源码可在这里下载。下面代码只显示部分方便阅读:
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__
以上是关于Objective-C try/catch异常处理机制原理。的主要内容,如果未能解决你的问题,请参考以下文章
Java异常处理机制:try...catch...的执行流程