Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 636|回复: 0

Objective-C try/catch异常处理机制原理。

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-4-26 10:18:08 | 显示全部楼层 |阅读模式

      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__

     

     

     

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-1-12 12:03 , Processed in 0.061662 second(s), 27 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表