如何在 Objective-C 的环境下实现 defer
关注仓库,及时获得更新: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 的功能的:]
ext_keywordify也是一个宏定义,它通过添加在宏之前添加autoreleasepool {}强迫onExit前必须加上@符号。#define ext_keywordify autoreleasepool {}
ext_cleanupBlock_t 是一个类型:
typedef void (^ext_cleanupBlock_t)();
metamacro_concat(ext_exitBlock_, __LINE__) 会将 ext_exitBlock 和当前行号拼接成一个临时的的变量名,例如:ext_exitBlock_19。
__attribute__((cleanup(ext_executeCleanupBlock), unused)) 将 cleanup 函数设置为 ext_executeCleanupBlock;并将当前变量 ext_exitBlock_19 标记为 unused 来抑制 Unused variable 警告。
变量 ext_exitBlock_19 的值为 ^{ NSLog("Log when out of scope."); },是一个类型为 ext_cleanupBlock_t 的 block。
在这个变量离开作用域时,会把上面的 block 的指针传入 cleanup 函数,也就是 ext_executeCleanupBlock:
void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) {
(*block)();
}
这个函数的作用只是简单的执行传入的 block,它满足了 GCC 文档中对 cleanup 函数的几个要求:
只能包含一个参数
参数的类型是一个指向变量类型的指针
函数的返回值是 void
总结
这是分析 libextobjc 框架的第一篇文章,也是比较简短的一篇,因为我们在日常开发中基本上用不到这个框架提供的 API,但是它依然会为我们展示了很多编程上的黑魔法。
libextobjc 将 cleanup 这一变量属性,很好地包装成了 @onExit,它的实现也是比较有意思的,也激起了笔者学习 GCC 编译命令并且阅读一些文档的想法。
如何在 Objective-C 的环境下实现 defer的更多相关文章
- 如何在ES5与ES6环境下处理函数默认参数
函数默认值是一个很提高鲁棒性的东西(就是让程序更健壮)MDN关于函数默认参数的描述:函数默认参数允许在没有值或undefined被传入时使用默认形参. ES5 使用逻辑或||来实现 众所周知,在ES5 ...
- 如何在Google Web Toolkit环境下Getshell
出品|MS08067实验室(www.ms08067.com) 本文作者:大盗贼卡卡 Google Web Toolkit简称(GWT),是一款开源Java软件开发框架.今天这篇文章会介绍如何在这样的环 ...
- 教你如何在Kali Linux 环境下设置蜜罐?
导读 Pentbox是一个包含了许多可以使渗透测试工作变得简单流程化的工具的安全套件.它是用Ruby编写并且面向GNU/Linux,同时也支持Windows.MacOS和其它任何安装有Ruby的系统. ...
- 4.1. 如何在Windows环境下开发Python
4.1. 如何在Windows环境下开发Python 4.1. 如何在Windows环境下开发Python 4.1.1. Python的最原始的开发方式是什么样的 4.1.1.1. 找个文本编辑器,新 ...
- Windows下搭建objective C开发环境
摘自:http://blog.csdn.net/zhanghefu/article/details/18320827 最近打算针对iPhone.iPod touch和iPad开发一些应用,所以,需要开 ...
- 有了SSL证书,如何在IIS环境下部署https?【转载】
昨天各位小伙伴都很开心的领取了自己的SSL证书,但是大部分小伙伴却不知道如何部署,也许是因为第一次接触SSL这种高端的东西吧,不过个人觉得就是懒懒懒...本来小编也挺懒的,但是答应了各位小伙伴的,那么 ...
- 如何在Windows环境下安装Linux系统虚拟机
如何在Windows环境下安装Linux系统虚拟机 本篇经验写给想要入门学习C语言的小白们.Windows系统因为使用窗口图形化,操作简单,功能多样,所以我们在Windows环境下可以做到很多,但想要 ...
- 有了SSL证书,如何在IIS环境下部署https?
昨天各位小伙伴都很开心的领取了自己的SSL证书,但是大部分小伙伴却不知道如何部署,也许是因为第一次接触SSL这种高端的东西吧,不过个人觉得就是懒懒懒...本来小编也挺懒的,但是答应了各位小伙伴的,那么 ...
- 如何在 Docker 环境下自动给 .NET 程序生成 Dump
前言 之前"一线码农"大佬有写文章介绍了如何在 windows 下自动 dump,正好手里有个在 docker 环境下 dump 的需求,所以在参考大佬文章的基础上,有了本篇. ...
随机推荐
- Android java.net.SocketException四大异常解决方案
java.net.SocketException如何才能更好的使用呢?这个就需要我们先要了解有关这个语言的相关问题.希望大家有所帮助.那么我们就来看看有关java.net.SocketExceptio ...
- Form.KeyPreview 属性2
在使用.Net Framework编写窗体应用程序的时候,有时有需要响应窗体的按键消息. 当窗体上没有任何其他控件的时候,窗体是可以直接响应这些消息的. 但是当窗体上有其他控件时,会发现窗体再也不会响 ...
- WCF - WAS Hosting
WCF - WAS Hosting To understand the concept of WAS hosting, we need to comprehend how a system is co ...
- java里的静态变量是放在了堆内存还是栈内存?
堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对 ...
- Win系统下制作U盘CLOVER引导+安装原版Mavericks10.9
啃苹果有一段时间了,之前一直用白苹果,但是白苹果配置有所限制,对于我搞音频的人来讲,显得有点拖沓.所以研究了将近2年的黑苹果,最近心血来潮给大家一个比较傻瓜式的教程,首先强调一点,黑苹果是需要折腾的, ...
- easyui 中的treegrid添加checkbox
<script type="text/javascript"> function show(checkid){ var s = '#check_'+checkid; / ...
- SQL Server 2008 数据库日志文件丢失处理方法
当数据库发生这种操作故障时,可以按如下操作步骤可解决此方法,打开数据库里的Sql 查询编辑器窗口,运行以下的命令. 1.修改数据库为紧急模式 ALTER DATABASE Zhangxing SET ...
- 从零开始学习jQuery (九) jQuery工具函数
一.摘要 本系列文章将带您进入jQuery的精彩世界, 其中有很多作者具体的使用经验和解决方案, 即使你会使用jQuery也能在阅读中发现些许秘籍. 我们经常要使用脚本处理各种业务逻辑, 最常见的就 ...
- Android从零单排之免费短信验证
介绍 短信验证功能大家都很熟悉了.在很多地方都能见到,注册新用户或者短息验证支付等.短信验证利用短信验证码来注册会员,大大降低了非法注册,很大程度上提高了用户账户的安全性. 目前市面上已经有了很多提供 ...
- cocos2d-x ios8 输入框显示bug
https://github.com/cocos2d/cocos2d-x/pull/8149