QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

如何在 Objective-C 的环境下实现 defer

  • 2019-12-10
  • 本文字数:2469 字

    阅读完需:约 8 分钟

如何在 Objective-C 的环境下实现 defer

关注仓库,及时获得更新:iOS-Source-Code-Analyze


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


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


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

关于 defer

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


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


Objective-C


func hello() {    defer {        print("4")    }    if true {        defer {            print("2")        }        defer {            print("1")        }    }    print("3")}
hello()
复制代码


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


1234
复制代码


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


如果对 defer 的作用仍然不是非常了解,可以看 guard & defer 这篇文章的后半部分。

Variable Attributes

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


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


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


Objective-C


#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 中必须传入只有一个参数的函数并且这个参数需要与变量的类型兼容


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


Objective-C


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 时都需要添加一个 @ 符号。


Objective-C


{    @onExit {        NSLog("Log when out of scope.");    };    NSLog("Log before out of scope.");}
复制代码


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


Objective-C


#define onExit \    ext_keywordify \    __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^
复制代码


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


Objective-C


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 {}
复制代码


  1. ext_cleanupBlock_t 是一个类型:


   typedef void (^ext_cleanupBlock_t)();
复制代码


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

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

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

  4. 在这个变量离开作用域时,会把上面的 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 编译命令并且阅读一些文档的想法。


关注仓库,及时获得更新:iOS-Source-Code-Analyze

关于图片和转载

本作品采用知识共享署名 4.0 国际许可协议进行许可。


  转载时请注明原文链接,图片在使用时请保留图片中的全部内容,可适当缩放并在引用处附上图片所在的文章链接,图片使用 Sketch 进行绘制,你可以在 [](https://draveness.me/draveness.me/sketch-sketch) 一文中找到画图的方法和素材。
复制代码


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/defer


2019-12-10 17:57613

评论

发布
暂无评论
发现更多内容

20年最新金九银十面试必备,教你一份文档吊打面试官,拿到offer

爱好编程进阶

Java 程序员 后端开发

CXF webservice之手动启动服务方法(restful )

爱好编程进阶

Java 程序员 后端开发

2021最新Java学习路线,自学者的福利

爱好编程进阶

Java 程序员 后端开发

想从单体架构演进到分布式架构,SBA 会是一个不错的选择

华为云开发者联盟

架构 微服务架构 分布式架构 单体架构 SBA

大规模并行分布式深度学习

阿里云大数据AI技术

人工智能 深度学习 并行分布式训练

卫剑钒:《大教堂与集市》被过誉了吗?

腾源会

开源 腾源会

Docker Swarm从部署到基本操作

爱好编程进阶

Java 程序员 后端开发

Dubbo如何处理业务异常,这个一定要知道哦!

爱好编程进阶

Java 程序员 后端开发

hadoop

爱好编程进阶

Java 程序员 后端开发

20 数据存储服务器集群的伸缩性设计

爱好编程进阶

Java 程序员 后端开发

2021最新分享Java面试题库万字精华 github上标星80

爱好编程进阶

Java 程序员 后端开发

@RequestParam、@ModelAttribute、

爱好编程进阶

Java 程序员 后端开发

Docker下Prometheus和Grafana三部曲之三:自定义监控项开发和配置

爱好编程进阶

Java 程序员 后端开发

小平邦彦:树懒style的世界一流数学家

图灵教育

数学 数学史 数学家

2021最新一次Java面试,快手三面一轮游,如今已拿意向书

爱好编程进阶

Java 程序员 后端开发

BS-XX-007基于JSP实现户籍管理系统

爱好编程进阶

Java 程序员 后端开发

dubbo实战之二:与SpringBoot集成

爱好编程进阶

Java 程序员 后端开发

Hexo 搭建:搭建与配置

爱好编程进阶

Java 程序员 后端开发

Java8-Stream:2万字20个实例,玩转集合的筛选

爱好编程进阶

Java 程序员 后端开发

HotSpot JVM 内存管理

爱好编程进阶

Java 程序员 后端开发

Java agent还不了解的程序员该反省一下了

爱好编程进阶

Java 程序员 后端开发

Java Script

爱好编程进阶

Java 程序员 后端开发

基于语义感知SBST的API场景测试智能生成

华为云开发者联盟

测试 语义感知 SBST 动态修正 ODG图

企业文档协作如何进行?

小炮

文档协同

web前端培训React调度器原理分析

@零度

前端开发 React

18 张图,一文了解 8 种常见的数据结构

爱好编程进阶

Java 程序员 后端开发

字节跳动构建Data Catalog数据目录系统的实践

字节跳动数据平台

数据库 字节跳动 数据治理 数据目录

中国全球GPU市场竞争格局分析

Finovy Cloud

人工智能 gpu 云服务器

28岁程序身价过亿,从字节提前“退休

爱好编程进阶

Java 程序员 后端开发

DockerFile的编写构建镜像步骤,常用命令和案例

爱好编程进阶

Java 程序员 后端开发

FusionStorage原理及组件

爱好编程进阶

Java 程序员 后端开发

如何在 Objective-C 的环境下实现 defer_语言 & 开发_Draveness_InfoQ精选文章