内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

libextobjc拾遗之onExit的实现

2018-03-12 17:09 出处:清屏网 人气: 评论(0

libextobjc 是一个非常牛逼的、充满了黑魔法的 OC 库,拓展了 OC 的能力,里面有非常多值得学习的东西。

本文主要聊聊 ExtScope 这个类中 onExit 宏的使用以及原理。

这个宏可以在代码块结束时做一些清理工作,无论是因为异常,还是 goto/break/continue/return 等语句导致代码块结束,都不影响清理工作的进行。

- (void)showUsage {
    @onExit {
        NSLog(@"Exit");
    };
    NSLog(@"End of method: showUsage");
}

整个宏的定义如下:

#define onExit \

    ext_keywordify \

    __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^

作用类似于 Swift 中的 defer,在结束代码块时做一些事。

很多人可能都知道 ext_keywordify 用于构造一个空的 autoreleasepool,这样可以要求使用者在前面强制加上 @

#define ext_keywordify autoreleasepool {}

实际上事情并非这么简单,autoreleasepool 的写法缺少编译器优化,多多少少会影响运行时性能。另一种可行的做法是利用 @try/@catch/@finally 这一语法。但它会导致编译器无法正确工作,漏掉一些原本可以检查出的错,比如:

- (void)tryCatchMissReturnTypeWarning {
    // This is block is used to test if foo and bar are the same, but this block has no return type
    // If `@autoreleasepool {}` is used, this code will not compile but now it does
    // Ths the usage of `@try {} @catch (...) {}` is dangaroud
    BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
        id foo = [[NSObject alloc] init];
        id bar = [[NSObject alloc] init];

        @try {} @catch (...) {}
//        @autoreleasepool {}
        NSLog(@"%@,%@", foo, bar);
    };
}

所以正确的做法是:

#if defined(DEBUG) && !defined(NDEBUG)
#define ext_keywordify autoreleasepool {}
#else
#define ext_keywordify try {} @catch (...) {}
#endif

这样 DEBUG 时不会漏掉错误,RELEASE 时又不影响性能

libextobjc 是一个非常牛逼的、充满了黑魔法的 OC 库,拓展了 OC 的能力,里面有非常多值得学习的东西。

本文主要聊聊 ExtScope 这个类中 onExit 宏的使用以及原理。

这个宏可以在代码块结束时做一些清理工作,无论是因为异常,还是 goto/break/continue/return 等语句导致代码块结束,都不影响清理工作的进行。

- (void)showUsage {
    @onExit {
        NSLog(@"Exit");
    };
    NSLog(@"End of method: showUsage");
}

整个宏的定义如下:

#define onExit \

    ext_keywordify \

    __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^

作用类似于 Swift 中的 defer,在结束代码块时做一些事。

很多人可能都知道 ext_keywordify 用于构造一个空的 autoreleasepool,这样可以要求使用者在前面强制加上 @

#define ext_keywordify autoreleasepool {}

实际上事情并非这么简单,autoreleasepool 的写法缺少编译器优化,多多少少会影响运行时性能。另一种可行的做法是利用 @try/@catch/@finally 这一语法。但它会导致编译器无法正确工作,漏掉一些原本可以检查出的错,比如:

- (void)tryCatchMissReturnTypeWarning {
    // This is block is used to test if foo and bar are the same, but this block has no return type
    // If `@autoreleasepool {}` is used, this code will not compile but now it does
    // Ths the usage of `@try {} @catch (...) {}` is dangaroud
    BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
        id foo = [[NSObject alloc] init];
        id bar = [[NSObject alloc] init];

        @try {} @catch (...) {}
//        @autoreleasepool {}
        NSLog(@"%@,%@", foo, bar);
    };
}

所以正确的做法是:

#if defined(DEBUG) && !defined(NDEBUG)
#define ext_keywordify autoreleasepool {}
#else
#define ext_keywordify try {} @catch (...) {}
#endif

这样 DEBUG 时不会漏掉错误,RELEASE 时又不影响性能

最后我们来看下比较关键的 onExit 的实现原理。我们先不考虑那一长串代码,看一个最简单的情况:

void func(int *a) {
    NSLog(@"Passed value is: %d\n", *a);
}

- (void)principle {
    int variable __attribute__((cleanup(func), unused)) = 2;
    NSLog(@"End of method: principle");
}

整个逻辑的核心就在于 int variable __attribute__((cleanup(block), unused)) = 2; 这一行,首先我们定义了一个变量,简单点写就是 int variable = 2 。只不过这个变量比较特别,它通过 __attribute__() 来修饰,这是 GNU C 的一种修饰符,用来描述变量的某些特性,就像 OC 的 property 有 strong/weak 等修饰符一样。

这里变量的修饰符用的是 cleanup(block) 和 unused。后者很好理解,就是把变量 variable 标记为 未使用的 ,告诉编译器:“我知道这个变量不会被用,它就是这么设计的,你就不要报 unused 的警告了”。

cleanup 是另一个修饰符,它需要提供一个函数,函数只能接受一个参数,且参数类型和 cleanup 修饰的变量相同,比如这里的 func 函数,参数必须是 int 类型,因为将来要传入的参数就是 int variable = 2

我们再梳理一遍目前的逻辑。简单来说,定义一个变量,并且把他标记为 cleanup ,然后再提供一个函数用于在代码块结束时调用,函数的参数就是这个变量。

至此,我们应该很清楚的想到,如果想在代码块结束时,执行任意代码,需要把要执行的代码放到一个 block 对象中,然后用 cleanup 去修饰这个 block 变量,至于要提供的函数,就非常简单了,可以是一个接受 block 作为参数的函数,函数内调用参数 block 即可。

现在可以看下 libextobjc 是怎么做的了:

__strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^

这里的 ext_cleanupBlock_t 其实是 block 的类型:

typedef void (^ext_cleanupBlock_t)(void);

变量名是 metamacro_concat(ext_exitBlock_, __LINE__) ,其实是把 ext_exitBlock_ 这个字符串常量和当前代码行数拼接起来,比如我们在第 20 行写 onExit 代码,生成的 block 变量名就是 ext_exitBlock_20

cleanup 所用的 ext_executeCleanupBlock 函数实现如下:

void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) {
    (*block)();
}

可见它就是一个用来执行 block 的模板函数。

最后还有一个 ^ ,用来作为 block 定义的开头,回头看看用法,现在是不是觉得不再是黑魔法了呢?

- (void)showUsage {
    @onExit {
        NSLog(@"Exit");
    };
    NSLog(@"End of method: showUsage");
}

libextobjc 是一个非常牛逼的、充满了黑魔法的 OC 库,拓展了 OC 的能力,里面有非常多值得学习的东西。

本文主要聊聊 ExtScope 这个类中 onExit 宏的使用以及原理。

这个宏可以在代码块结束时做一些清理工作,无论是因为异常,还是 goto/break/continue/return 等语句导致代码块结束,都不影响清理工作的进行。

- (void)showUsage {
    @onExit {
        NSLog(@"Exit");
    };
    NSLog(@"End of method: showUsage");
}

整个宏的定义如下:

#define onExit \

    ext_keywordify \

    __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^

作用类似于 Swift 中的 defer,在结束代码块时做一些事。

很多人可能都知道 ext_keywordify 用于构造一个空的 autoreleasepool,这样可以要求使用者在前面强制加上 @

#define ext_keywordify autoreleasepool {}

实际上事情并非这么简单,autoreleasepool 的写法缺少编译器优化,多多少少会影响运行时性能。另一种可行的做法是利用 @try/@catch/@finally 这一语法。但它会导致编译器无法正确工作,漏掉一些原本可以检查出的错,比如:

- (void)tryCatchMissReturnTypeWarning {
    // This is block is used to test if foo and bar are the same, but this block has no return type
    // If `@autoreleasepool {}` is used, this code will not compile but now it does
    // Ths the usage of `@try {} @catch (...) {}` is dangaroud
    BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
        id foo = [[NSObject alloc] init];
        id bar = [[NSObject alloc] init];

        @try {} @catch (...) {}
//        @autoreleasepool {}
        NSLog(@"%@,%@", foo, bar);
    };
}

所以正确的做法是:

#if defined(DEBUG) && !defined(NDEBUG)
#define ext_keywordify autoreleasepool {}
#else
#define ext_keywordify try {} @catch (...) {}
#endif

这样 DEBUG 时不会漏掉错误,RELEASE 时又不影响性能

最后我们来看下比较关键的 onExit 的实现原理。我们先不考虑那一长串代码,看一个最简单的情况:

void func(int *a) {
    NSLog(@"Passed value is: %d\n", *a);
}

- (void)principle {
    int variable __attribute__((cleanup(func), unused)) = 2;
    NSLog(@"End of method: principle");
}

整个逻辑的核心就在于 int variable __attribute__((cleanup(block), unused)) = 2; 这一行,首先我们定义了一个变量,简单点写就是 int variable = 2 。只不过这个变量比较特别,它通过 __attribute__() 来修饰,这是 GNU C 的一种修饰符,用来描述变量的某些特性,就像 OC 的 property 有 strong/weak 等修饰符一样。

这里变量的修饰符用的是 cleanup(block) 和 unused。后者很好理解,就是把变量 variable 标记为 未使用的 ,告诉编译器:“我知道这个变量不会被用,它就是这么设计的,你就不要报 unused 的警告了”。

cleanup 是另一个修饰符,它需要提供一个函数,函数只能接受一个参数,且参数类型和 cleanup 修饰的变量相同,比如这里的 func 函数,参数必须是 int 类型,因为将来要传入的参数就是 int variable = 2

我们再梳理一遍目前的逻辑。简单来说,定义一个变量,并且把他标记为 cleanup ,然后再提供一个函数用于在代码块结束时调用,函数的参数就是这个变量。

至此,我们应该很清楚的想到,如果想在代码块结束时,执行任意代码,需要把要执行的代码放到一个 block 对象中,然后用 cleanup 去修饰这个 block 变量,至于要提供的函数,就非常简单了,可以是一个接受 block 作为参数的函数,函数内调用参数 block 即可。

现在可以看下 libextobjc 是怎么做的了:

__strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^

这里的 ext_cleanupBlock_t 其实是 block 的类型:

typedef void (^ext_cleanupBlock_t)(void);

变量名是 metamacro_concat(ext_exitBlock_, __LINE__) ,其实是把 ext_exitBlock_ 这个字符串常量和当前代码行数拼接起来,比如我们在第 20 行写 onExit 代码,生成的 block 变量名就是 ext_exitBlock_20

cleanup 所用的 ext_executeCleanupBlock 函数实现如下:

void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) {
    (*block)();
}

可见它就是一个用来执行 block 的模板函数。

最后还有一个 ^ ,用来作为 block 定义的开头,回头看看用法,现在是不是觉得不再是黑魔法了呢?

- (void)showUsage {
    @onExit {
        NSLog(@"Exit");
    };
    NSLog(@"End of method: showUsage");
}

分享给小伙伴们:
本文标签: libextobjconExit

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号