什么是 defer ?
defer
是一种延迟执行机制,就是希望某一段代码能在前面写,但是能够在后面(比如作用域末尾)执行。最在 iOS 开发中,会用到这样一些成对使用的函数,比如 UIGraphicsBeginImageContext
与 UIGraphicsEndImageContext
以及其它很多 CG
、CF
函数,再如数据库打开后需要关闭。如果这些成对出现的操作之间,业务逻辑比较长,或者需要多次返回,那么就很容易遗忘或者重复写多次,因此编译器提供了清理函数的功能 __attribute__((cleanup(clean_func)))
,而本文即将介绍的 defer
也是基于此功能实现的。
安装
推荐使用 CocoaPods 安装到项目中:
pod 'XZKit/XZKitConstants'
XZKitConstants
是 XZKit 框架的基础模块,非常轻量级,只有一些常用函数、宏定义和类目。
效果
代码示例
有了 defer
功能,于是妈妈再也不同担心成对的方法忘记调用了。比如代码绘制图片,以前这么写:
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
// ...
if (condition1) {
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// ...
if (condition2) {
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// ...
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
有了 defer
就可以这么写了:
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
defer(^{
UIGraphicsEndImageContext();
});
// ...
if (condition1) { // 条件 condition1 下绘制的图片
return UIGraphicsGetImageFromCurrentImageContext();
}
// ...
if (condition2) { // 条件 condition2 下绘制的图片
return UIGraphicsGetImageFromCurrentImageContext();
}
// ...
return UIGraphicsGetImageFromCurrentImageContext();
怎么样,写起来清爽了很多有木有!
原理分析
功能 defer
主要用到了 GNU C 编译器提供 __attribute__
机制,该机制可以给变量、函数、类型添加一些编译属性,优化代码在编译时的行为。比如功能 defer
用到的 __attribute__((cleanup(clean_func)))
这个属性,可以告诉编译器,在变量超出作用域时执行 clean_func
清理函数,该清理函数只接受一个参数,就是它所修饰的变量的指针。这是编译时的行为,编译器在分析代码后,在变量作用域的末尾添加上调用清理函数的操作,并最终编译成二进制序列。
所以分析 XZKit 的源码可以发现,每次 defer
宏调用,实际上是在该作用域定义了一个 block 局部变量,即 defer
中的 block
代码块;并对这个 block 变量添加了编译属性,告诉编译器在该变量超出作用域时,将 block 变量的地址传递给 __xz_defer__
清理函数执行。
以下是源码,里面涉及一个高级宏 __COUNTER__
每次调用值自增的宏,以保证每次使用 defer
都能声明一个唯一的变量名。
// .h 文件。
extern void __xz_defer__(void (^*operation)(void));
#undef defer
#define __xz_defer_var_v__(X, Y) X##Y
#define __xz_defer_var_m__(X, Y) __xz_defer_var_v__(X, Y)
#define defer(statements) void(^__xz_defer_var_m__(__xz_defer_var_, __COUNTER__))(void) __attribute__((cleanup(__xz_defer__), unused)) = statements
// .m 文件
void __xz_defer__(void (^*operation)(void)) {
(*operation)();
}
建议为 defer
添加 Code Snippet
方便调用,当然你也可以像 XZKit 那样,在 #undef defer
之前声明一个同名函数,只是这样的话,代码提示列表会出现两个 defer
选项。
如果觉得该功能不错的话,上 GitHub 给个 Star 吧!