关注仓库,及时获得更新:https://github.com/draveness/iOS-Source-Code-Analyze

Follow: https://github.com/Draveness

这篇文章会对 libextobjc 中的一小部分代码进行分析,也是如何扩展 Objective-C 语言系列文章的第一篇,笔者会从 libextobjc 中选择一些黑魔法进行介绍。

对 Swift 稍有了解的人都知道,defer 在 Swift 语言中是一个关键字;在 defer 代码块中的代码,会在作用域结束时执行。在这里,我们会使用一些神奇的方法在 Objective-C 中实现 defer。

如果你已经非常了解 defer 的作用,你可以跳过第一部分的内容,直接看 Variable Attributes。

关于 defer

defer 是 Swift 在 2.0 时代加入的一个关键字,它提供了一种非常安全并且简单的方法声明一个在作用域结束时执行的代码块。

如果你在 Swift Playground 中输入以下代码:

func hello() {

defer {

print("4")

}

if true {

defer {

print("2")

}

defer {

print("1")

}

}

print("3")

}

hello()

控制台的输出会是这样的:

1

2

3

4

你可以仔细思考一下为什么会有这样的输出,并在 Playground 使用 defer 写一些简单的代码,相信你可以很快理解它是如何工作的。

如果对 defer 的作用仍然不是非常了解,可以看 guard & defer 这篇文章的后半部分。http://nshipster.com/guard-and-defer/#defer

Variable Attributes

libextobjc 实现的 defer 并没有基于 Objective-C 的动态特性,甚至也没有调用已有的任何方法,而是使用了 Variable Attributes 这一特性。

同样在 GCC 中也存在用于修饰函数的 Function Attributes

Variable Attributes 其实是 GCC 中用于描述变量的一种修饰符。我们可以使用 __attribute__ 来修饰一些变量来参与静态分析等编译过程;而在 Cocoa Touch 中很多的宏其实都是通过 __attribute__ 来实现的,例如:

#define NS_ROOT_CLASS __attribute__((objc_root_class))

而 cleanup 就是在这里会使用的变量属性:

The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.

GCC 文档中对 cleanup 属性的介绍告诉我们,在 cleanup 中必须传入只有一个参数的函数并且这个参数需要与变量的类型兼容。

如果上面这句比较绕口的话很难理解,可以通过一个简单的例子理解其使用方法:

void cleanup_block(int *a) {

printf("%d\n", *a);

}

int variable __attribute__((cleanup(cleanup_block))) = 2;

在 variable 这个变量离开作用域之后,就会自动将这个变量的指针传入 cleanup_block 中,调用 cleanup_block 方法来进行『清理』工作。

实现 defer

到目前为止已经有了实现 defer 需要的全部知识,我们可以开始分析 libextobjc 是怎么做的。

在 libextobjc 中并没有使用 defer 这个名字,而是使用了 onExit(表示代码是在退出作用域时执行)

为了使 onExit 在使用时更加明显,libextobjc 通过一些其它的手段使得我们在每次使用 onExit 时都需要添加一个 @ 符号。

{

@onExit {

NSLog("Log when out of scope.");

};

NSLog("Log before out of scope.");

}

onExit 其实只是一个精心设计的宏:

#define onExit \

ext_keywordify \

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

既然它只是一个宏,那么上面的代码其实是可以展开的:

autoreleasepool {}

__strong ext_cleanupBlock_t ext_exitBlock_19 __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^ {

NSLog("Log when out of scope.");

};

这里,我们分几个部分来分析上面的代码片段是如何实现 defer 的功能的:]

  1. ext_keywordify 也是一个宏定义,它通过添加在宏之前添加 autoreleasepool {} 强迫 onExit 前必须加上 @ 符号。

    #define ext_keywordify autoreleasepool {}

  2. ext_cleanupBlock_t 是一个类型:

    typedef void (^ext_cleanupBlock_t)();

  3. metamacro_concat(ext_exitBlock_, __LINE__) 会将 ext_exitBlock 和当前行号拼接成一个临时的的变量名,例如:ext_exitBlock_19。

  4. __attribute__((cleanup(ext_executeCleanupBlock), unused)) 将 cleanup 函数设置为 ext_executeCleanupBlock;并将当前变量 ext_exitBlock_19 标记为 unused 来抑制 Unused variable 警告。

  5. 变量 ext_exitBlock_19 的值为 ^{ NSLog("Log when out of scope."); },是一个类型为 ext_cleanupBlock_t 的 block。

  6. 在这个变量离开作用域时,会把上面的 block 的指针传入 cleanup 函数,也就是 ext_executeCleanupBlock:

    void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) {

    (*block)();

    }

    这个函数的作用只是简单的执行传入的 block,它满足了 GCC 文档中对 cleanup 函数的几个要求:

  1. 只能包含一个参数

  2. 参数的类型是一个指向变量类型的指针

  3. 函数的返回值是 void

总结

 

这是分析 libextobjc 框架的第一篇文章,也是比较简短的一篇,因为我们在日常开发中基本上用不到这个框架提供的 API,但是它依然会为我们展示了很多编程上的黑魔法。

libextobjc 将 cleanup 这一变量属性,很好地包装成了 @onExit,它的实现也是比较有意思的,也激起了笔者学习 GCC 编译命令并且阅读一些文档的想法。

如何在 Objective-C 的环境下实现 defer的更多相关文章

  1. 如何在ES5与ES6环境下处理函数默认参数

    函数默认值是一个很提高鲁棒性的东西(就是让程序更健壮)MDN关于函数默认参数的描述:函数默认参数允许在没有值或undefined被传入时使用默认形参. ES5 使用逻辑或||来实现 众所周知,在ES5 ...

  2. 如何在Google Web Toolkit环境下Getshell

    出品|MS08067实验室(www.ms08067.com) 本文作者:大盗贼卡卡 Google Web Toolkit简称(GWT),是一款开源Java软件开发框架.今天这篇文章会介绍如何在这样的环 ...

  3. 教你如何在Kali Linux 环境下设置蜜罐?

    导读 Pentbox是一个包含了许多可以使渗透测试工作变得简单流程化的工具的安全套件.它是用Ruby编写并且面向GNU/Linux,同时也支持Windows.MacOS和其它任何安装有Ruby的系统. ...

  4. 4.1. 如何在Windows环境下开发Python

    4.1. 如何在Windows环境下开发Python 4.1. 如何在Windows环境下开发Python 4.1.1. Python的最原始的开发方式是什么样的 4.1.1.1. 找个文本编辑器,新 ...

  5. Windows下搭建objective C开发环境

    摘自:http://blog.csdn.net/zhanghefu/article/details/18320827 最近打算针对iPhone.iPod touch和iPad开发一些应用,所以,需要开 ...

  6. 有了SSL证书,如何在IIS环境下部署https?【转载】

    昨天各位小伙伴都很开心的领取了自己的SSL证书,但是大部分小伙伴却不知道如何部署,也许是因为第一次接触SSL这种高端的东西吧,不过个人觉得就是懒懒懒...本来小编也挺懒的,但是答应了各位小伙伴的,那么 ...

  7. 如何在Windows环境下安装Linux系统虚拟机

    如何在Windows环境下安装Linux系统虚拟机 本篇经验写给想要入门学习C语言的小白们.Windows系统因为使用窗口图形化,操作简单,功能多样,所以我们在Windows环境下可以做到很多,但想要 ...

  8. 有了SSL证书,如何在IIS环境下部署https?

    昨天各位小伙伴都很开心的领取了自己的SSL证书,但是大部分小伙伴却不知道如何部署,也许是因为第一次接触SSL这种高端的东西吧,不过个人觉得就是懒懒懒...本来小编也挺懒的,但是答应了各位小伙伴的,那么 ...

  9. 如何在 Docker 环境下自动给 .NET 程序生成 Dump

    前言 之前"一线码农"大佬有写文章介绍了如何在 windows 下自动 dump,正好手里有个在 docker 环境下 dump 的需求,所以在参考大佬文章的基础上,有了本篇. ​ ...

随机推荐

  1. Android 编译大全

    http://quanminchaoren.iteye.com/blog/840917

  2. vi/vim 基本使用

    摘要: 在minicom终端里修改开发板中的文件时,必须要用到vi,因为开发板中并不像开发主机那样,有gedit和kscope这样的编辑器:还有,即便是在开发主机上,也会经常用到vi,因为vi使用起来 ...

  3. 【PythonChallenge】Level 3

    题目为正则表达式,需要注意EXACTLY的含义,即AAAAxBBB中x不满足条件,但aAAAxBBBa却满足条件.使用perl解决此题,利用s///替换字母,循环读取整个源码文件,结果为linkedl ...

  4. Android开发UI之补间动画-布局添加动画

    布局添加动画 使用步骤: 1.获取到布局的id RelativeLayout ly=(RelativeLayout)findViewById(R.id.layout); 2.设置动画样式 ScaleA ...

  5. bzoj1069

    四边形的问题可以转化为三角形处理穷举对角线,然后处理上下两个三角形,旋转卡壳 ..] of double; q:..] of longint; l,r,i,j,k,t,n:longint; ans:d ...

  6. bzoj1040

    论环形dp的重要! 其实这个环比较简单,稍微分析一下就知道, 这是一个简单环,并且每个联通块里只含有一个. 我觉得把处理环的关键,就是要找出环形和线形(树形)有什么区别. 如果我们从某处断开的做dp的 ...

  7. BZOJ_1010_[HNOI2008]_玩具装箱toy_(斜率优化动态规划+单调队列)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1010 给出\(n\)和\(l\).有\(n\)个玩具,第\(i\)个玩具的长度是\(c[i]\ ...

  8. Discuz! x3.1的插件/utility/convert/index.php代码执行漏洞

    漏洞版本: Discuz! x3.1及以下版本 漏洞描述: Discuz! x3.1的插件/utility/convert/index.php存在代码执行漏洞,如果用户在使用完之后不删除,会导致网站容 ...

  9. 手机端的mousedown

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  10. PowerDesigner一些小技巧

    1.安装PD v12.0版 2.由pdm生成建表脚本时,字段超过15字符就发生错误(oracle) 原因未知,解决办法是打开PDM后,会出现Database的菜单栏,进入Database - Edit ...