写点什么

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

评论

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

IoT存量设备跨账号迁移,动态切换region节点,公共区到企业实例迁移方案——实践类

阿里云AIoT

物联网 API 应用服务中间件 储存

Zepoch节点持有人数大突破,Nautilus Chain 或有海量空投

股市老人

Easyrecovery16汉化版电脑数据恢复软件

茶色酒

EasyRecovery16

时序数据库 CeresDB 1.0 正式发布

TRaaS

2022 IoTDB Summit:IoTDB PMC Chair 黄向东《Apache IoTDB 1.0 全新版本》

Apache IoTDB

数据库·

ChatGPT 不是黑魔法,“替代搜索引擎”言之尚早

Zilliz

搜索引擎 ChatGPT

揭秘可视化图探索工具 NebulaGraph Explore 是如何实现图计算的

NebulaGraph

可视化 图数据库

从ChatGPT到MOSS,《流浪地球2》是否会照进现实?

加入高科技仿生人

人工智能 AI 智能化 ChatGPT New Bing

不为人知的网络编程(十五):深入操作系统,一文搞懂Socket到底是什么

JackJiang

IoT物联网平台通信用Topic梳理——实践类

阿里云AIoT

物联网

打谱编曲软件GuitarPro8.0中文版下载

茶色酒

GuitarPro

MyDumper工具介绍

领创集团Advance Intelligence Group

MySQL 多线程 dba MyDumper

Windows平台上也可以进行iOS App的开发

雪奈椰子

ios app证书

Log4j on Cloud 如何在云上看日志

纳速云

elasticsearch Serverless 日志 log4j java

更安全更稳定,阿里云斩获多项云系统稳定安全运行优秀案例

阿里巴巴云原生

阿里云 云原生 安全

开心档之Vue教程1

雪奈椰子

开心档之Vue3

雪奈椰子

Vue 开心档

一张自拍即可实现变老变年轻,带你感受时光流逝之美

极客飞兔

Python 人工智能 计算机视觉 图像处理 百度飞桨

蚂蚁集团智能可观测平台 HoloInsight 正式开源

TRaaS

开心档之Vue教程4

雪奈椰子

VUE 3.0 源码 开心档

证书申请教程

雪奈椰子

iOS上架 app证书

用AppUploader轻松上传IPA文件

雪奈椰子

开心档

业务系统从阿里云物联网平台获取设备数据的3种方案——实践类

阿里云AIoT

数据库 物联网 存储 数据处理 消息中间件

IoT离线设备云端控制指令消息触达方案——实践类

阿里云AIoT

物联网 API 网络性能优化

如何提高大数据传输的安全性

镭速

开心档之Vue教程2

雪奈椰子

Vue 开心档

开心档之Vue5

雪奈椰子

Vue 开心档

什么是bundle id

雪奈椰子

ios app证书

快速排查IoT物联网平台问题实用手册——实践类

阿里云AIoT

物联网

mperf:移动/嵌入式平台算子性能调优利器

MegEngineBot

性能分析 移动端 高性能计算

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