写点什么

Swift Runtime 动态性分析

  • 2016-03-31
  • 本文字数:2836 字

    阅读完需:约 9 分钟

Swift 是苹果 2014 年发布的编程开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。Swift 已经开源,目前最新版本为 2.2。我们知道 Objective-C 是具有动态性的,能够通过 runtime API 调用和替换任意方法,那 Swift 也具有这些动态性吗?

分析用例

我们拿一个纯 Swift 类和一个继承自 NSObject 的类来做分析,这两个类里包含尽量多的 Swift 的类型比如 Character、String、AnyObject、Tuple。

代码如下:

(点击放大图像)

方法、属性

动态性比较重要的一点就是能够拿到某个类所有的方法、属性,我们使用如下代码来打印方法和属性列表。

(点击放大图像)

调用showClsRuntime 的代码如下:

复制代码
let aSwiftClass:TestASwiftClass = TestASwiftClass();
showClsRuntime(object_getClass(aSwiftClass));
print("\n");
showClsRuntime(object_getClass(self));

看看我们得到什么结果?

(点击放大图像)

* 对于纯 Swift 的 TestASwiftClass 来说任何方法、属性都未获取到

* 对于 TestSwiftVC 来说除testReturnTuple

testReturnVoidWithaCharacter两个方法外,其他的都获取成功了。

这是为什么?

  • 纯 Swift 类的函数调用已经不再是 Objective-c 的运行时发消息,而是类似 C++ 的 vtable,在编译时就确定了调用哪个函数,所以没法通过 runtime 获取方法、属性。
  • TestSwiftVC 继承自UIViewController,基类为NSObject,而 Swift 为了兼容 Objective-C,凡是继承自 NSObject 的类都会保留其动态性,所以我们能通过 runtime 拿到他的方法。

但为什么testReturnTuple

testReturnVoidWithaCharacter却又获取不到呢?

从 Objective-c 的 runtime 特性可以知道,所有运行时方法都依赖 TypeEncoding ,也就是 method_getTypeEncoding 返回的结果,他指定了方法的 _ 参数类型 _ 以及在函数调用时参数入栈所要的 _ 内存空间 _,没有这个标识就无法动态的压入参数(比如 testReturnVoidWithaId: Optional(“v24@0:8@16”) Optional(“v”),表示此方法参数共需 24 个字节,返回值为 void,第一个参数为 id,第二个为 selector,第三个为 id),而 Character 和 Tuple 是 Swift 特有的,无法映射到 OC 的类型,更无法用 OC 的 typeEncoding 表示,也就没法通过 runtime 获取了。

Method Swizzling

动态性最常用的就是方法替换(Method Swizzling),将类的某个方法替换成自定义的方法,从而达到 hook 的作用。

  • 对于纯 Swift 类(如 TestASwiftClass)来说,无法通过 objc runtime 替换方法,因为由上面的测试可知拿不到这些方法、属性
  • 对于继承自 NSObject 类(如 TestSwiftVC)来说,无法通过 runtime 获取到的方法肯定没法替换了。那能通过 runtime 获取到的方法就都能被替换吗?我们测一把。Method Swizzling 的代码如下

(点击放大图像)

我们替换两个可以被runtime 获取到的方法: viewDidAppeartestReturnVoidWithaId

(点击放大图像)

打印的日志为

复制代码
F:testReturnVoidWithaId L:50
F:sz_viewDidAppear L:46

说明 viewDidAppear 已经被替换,但是 testReturnVoidWithaId 却没有被替换,这是为何?

我们在方法里打个断点看看,如图:

(点击放大图像)

(点击放大图像)

可以看到区别,调用sz_viewDidAppear 栈的前一帧为 @objc TestSwiftVC.sz_viewDidAppear(Bool) -> ()有个@objc标识,而调用 testReturnVoidWithaId 则没有此标识。

@objc用来做什么的?与动态性有关吗?

@objc

找到官方文档读读。

可以知道@objc 是用来将Swift 的API 导出给Objective-C 和Objective-C runtime 使用的,如果你的类继承自Objective-c 的类(如NSObject)将会自动被编译器插入@objc 标识。

我们在把TestASwiftClass(纯Swift 类)的方法、属性前都加个@objc 试试,如图:

(点击放大图像)

查看日志可以发现加了@objc 的方法、属性均可以被runtime 获取到了。

(点击放大图像)

dynamic

文档里还有一句说明:

加了 @objc 标识的方法、属性无法保证都会被运行时调用,

因为 Swift 会做静态优化。要想完全被动态调用,必须使用 dynamic 修饰。

使用 dynamic 修饰将会隐式的加上 @objc 标识

这也就解释了为什么 testReturnVoidWithaId 无法被替换,因为写在 Swift 里的代码直接被编译优化成静态调用了。

而 viewDidAppear 是继承 Objective-C 类获得的方法,本身就被修饰为 dynamic,所以能被动态替换。

我们把 TestSwiftVC 方法前加上 dynamic 再测一把,如图:

(点击放大图像)

从堆栈也可以看出,方法的调用前增加了@objc 标识,testReturnVoidWithaId 方法被替换成功了。

同样的做法,我们把TestASwiftClass 的方法和属性也都加上dynamic 修饰,做Method Swizzling,同样获得成功,如图

(点击放大图像)

Objective-C 获取 Swift runtime 信息

在 Objective-c 代码里使用objc_getClass("TestSwiftVC");会发现返回值为空,这是为什么?Swift 代码中的 TestSwiftVC 类,在 OC 中还是这个名字吗?

我们初始化一个对象,并断点和打印看看,如下图:

(点击放大图像)

可以看到Swift 中的TestSwiftVC 类在OC 中的类名已经变成 TestSwift.TestSwiftVC,即规则为SWIFT_MODULE_NAME. 类名称,在普通源码项目里 SWIFT_MODULE_NAME 即为 ProductName,在打好的 Cocoa Touch Framework 里为则为导出的包名。

所以要想从 Objective-c 中获取 Swift 类的 runtime 信息得这样写:

复制代码
id cls = objc_getClass("TestSwift.TestASwiftClass");
showClsRuntime(cls);
id cls2 = objc_getClass("TestSwift.TestSwiftVC");
showClsRuntime(cls2);

Objective-C 替换 Swift 函数

给 TestSwiftVC 和 TestASwiftClass 的testReturnVoidWithaId函数加上 dynamic 修饰,然后我们在 Objective-C 代码里替换为testReturnVoidWithaIdImp函数:

(点击放大图像)

运行之后我们得到结果

复制代码
F:void testReturnVoidWithaIdImp(__strong id, SEL, __strong id) L:20 self=<TestSwift.TestSwiftVC: 0x7fb4e1d148f0>
F:void testReturnVoidWithaIdImp(__strong id, SEL, __strong id) L:20 self=TestSwift.TestASwiftClass

说明两者的方法在加上 dynamic 修饰后,均能在 Objective-c 里被替换。(TestSwiftVC 的 testReturnVoidWithaId 不加 dynamic 也会打印日志,为什么?留给读者思考)

总结

  • 纯 Swift 类没有动态性,但在方法、属性前添加 dynamic 修饰可以获得动态性。
  • 继承自 NSObject 的 Swift 类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加 dynamic 修饰才可以获得动态性。
  • 若方法的参数、属性类型为 Swift 特有、无法映射到 Objective-C 的类型 (如 Character、Tuple),则此方法、属性无法添加 dynamic 修饰(会编译错误)
  • Swift 类在 Objective-C 中会有模块前缀

本文作者

尹峥伟(花名 君展),来自手机淘宝技术团队的资深无线开发工程师,主要负责手机淘宝基础架构研发,github 开源库 Wax 的维护者,微信号 yzwlvzxh,微博 @君展。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-03-31 12:0710593

评论

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

解析 RocketMQ 多样消费功能-消息过滤

阿里巴巴云原生

阿里云 RocketMQ 云原生

Base64码常见操作(url链接文件转base64编码、本地文件转base64编码等)

共饮一杯无

Java base64 11月月更

DevOps 必备的 Kubernetes 安全清单

SEAL安全

Kubernetes DevOps 安全

Awesome MegEngineer 英雄招募帖,开源社区专属权益等你来领

MegEngineBot

深度学习 开源 MegEngine 开发者福利

一文带你回顾操作系统的内存知识点

华为云开发者联盟

操作系统 开发 内存 华为云

IM通讯协议专题学习(二):快速理解Protobuf的背景、原理、使用、优缺点

JackJiang

【C语言】goto 关键字

謓泽

11月月更

初步探索GraalVM--云原生时代JVM黑科技

京东科技开发者

Java lua jdk 云原生 GraalVM

kubernetes下jenkins实战maven项目编译构建

程序员欣宸

DevOps jenkins 11月月更

软件测试校招面试题 | 实习生和应届生有什么区别?

测试人

面试 软件测试 自动化测试 测试开发 实习

千万级学生管理系统设计试卷存储方案

Geek_92ba6f

加密算法是什么?有哪几种类型?有什么用?

行云管家

加密算法

聚焦亮点,西安人工智能治理委员会成立暨产业政策白皮书正式发布

极客天地

单实例并发超1个亿!阿里云飞天洛神云网络NLB网络型负载均衡性能重大突破

云布道师

负载均衡 阿里云 云网络

房产|1-10月全国房地产开发投资数据解读

前嗅大数据

视频清晰度优化指南

得物技术

深度学习 算法 H.265 视频质量 图像超分

探知数字化研发4 - 底座篇

薛飞

数字化研发 数字化底座

收藏|多指标时序预测方式及时序特征工程总结

云智慧AIOps社区

人工智能 机器学习 深度学习 时间序列 时间序列预测

旺链科技创始人刘涛荣登“中国区块链60人”榜单

旺链科技

区块链 数字经济 产业区块链 企业号十月PK榜

Ernie-SimCSE对比学习在内容反作弊上应用

百度Geek说

人工智能 AI技术 企业号十月 PK 榜

特种设备如何管理?不同岗位视角职责解析

PreMaint

设备管理 特种设备

房产|2022年10月房价数据出炉!房价上涨的城市仅有…

前嗅大数据

洞见科技姚明:隐私计算行业将会发展为多层级多领域的数据智能流通网络

洞见科技

DTSE Tech Talk | 第11期:深入浅出畅谈华为云低时延直播技术

华为云开发者联盟

云计算 后端 华为云

CSS学习笔记(九)

lxmoe

CSS 前端 学习笔记 11月月更

看完这篇线程、线程锁与线程池讲解,面试随便问!

小小怪下士

Java 程序员 面试 线程 线程池

HUAWEI DevEco Studio 3.1版本发布,配套ArkTS声明式开发全面升级

HarmonyOS开发者

HarmonyOS

node.js的path路径模块和http模块

急需上岸的小谢

11月月更

HMS Core手语服务荣获2022中国互联网大会“特别推荐案例”:助力建设数字社会

HarmonyOS SDK

手语 HMS Core

7.PGL图学习之图游走类metapath2vec模型[系列五]

汀丶人工智能

图神经网络 GNN GCN 11月月更

关于HTTPDNS,你知道多少?

移动研发平台EMAS

阿里云 网络 HTTP #EMAS

Swift Runtime动态性分析_语言 & 开发_尹峥伟_InfoQ精选文章