写点什么

如何在 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:57556

评论

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

读《Software Engineering at Google》(09)

术子米德

架构师成长笔记

upnp.exe进程

Sher10ck

日积月累

API对接之模板方法

Rubble

4月日更 4月月更

web前端培训javaScript的内存管理机制分享

@零度

JavaScript 前端开发

Spring入门基础

乌龟哥哥

4月月更

预售2小时,破10000册!顶级投资人的投资策略首度全面公开

博文视点Broadview

TASKCTL C/S客户端两种不同的登陆模式

敏捷调度TASKCTL

分布式 ETL 批量操作 自动化运维 调度任务

什么是知识库管理系统?如何搭建企业知识库系统?

小炮

企业知识管理 企业知识管理工具 知识管理系统

打破虚拟边界的视频交互新方式,AR隔空书写的应用理念和探索实践

阿里云视频云

音视频 AR 直播 视频云

Go 入门很简单:Writer和Reader接口

宇宙之一粟

接口 Go 语言 4月月更

Nocalhost - 让云原生时代的开发更高效

沃趣科技

云原生 Nocalhost 应用开发

MySQL 无法满足查询性能?北明天时选择 TDengine 实现热网监控和能源分析

TDengine

数据库 tdengine 开源 时序数据库

蓝翔:百度开源深度学习平台飞桨的核心技术及应用

百度开发者中心

Linux驱动开发-外部中断的注册使用(按键为例)

DS小龙哥

4月月更

2021最新Spring Boot 面试题

爱好编程进阶

Java 面试 后端开发

老旧项目二次开发指南

阿毛

重构 项目架构 二次开发

Docker 实战教程之从入门到提高 (七)

汪子熙

Docker 容器 docker image 容器镜像 4月月更

一文读懂在OpenHarmony轻量设备开发应用

OpenHarmony开发者

OpenHarmony OpenHarmony应用开发 轻量设备

TASKCTL 作业异常报错如何发送短信和邮件

敏捷调度TASKCTL

开源 DevOps 分布式 方法论 敏捷开发

spring-cloud-kubernetes的服务发现和轮询实战(含熔断)

程序员欣宸

java 4月月更

高效压缩位图在推荐系统中的应用

vivo互联网技术

redis 推荐 存储

不同阶段的人,如何学习Rust?加入非凸,一起学习!

非凸科技

rust 招聘 编程语言‘

读《Software Engineering at Google》(08)

术子米德

架构师成长笔记

读《Software Engineering at Google》(10)

术子米德

架构师成长笔记

java培训JVM内存模型和GC机制的解析

@零度

Java JVM GC

大数据培训Hive面试核心知识点分享

@零度

大数据 hive

建木持续集成平台v2.3.0发布

Jianmu

持续集成 工作流 gitops pipeline 建木CI

2021最新最全Java基础高频面试题汇总(1W字详细解析)

爱好编程进阶

Java 面试 后端开发

技术文档|基于双目感知的封闭园区自动驾驶搭建--感知适配

百度开发者中心

基于云效Codeup一键恢复删库保护数据资源,程序员删库跑路不复存在

阿里云云效

云计算 阿里云 程序员 代码安全 删库保护

30 网站架构师职场攻略

爱好编程进阶

Java 面试 后端开发

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