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

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:0710454

评论

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

用友BIP新零售产品发布,与零售企业共创新未来

用友BIP

新零售 数字营销

周家恩:GaussDB(for MySQL)云原生数据库技术演进和挑战

NineData

MySQL 数据库 GaussDB GaussDB(for MySQL) 华为自研数据库

Midjourney|文心一格prompt教程[Text Prompt(上篇)]:品牌log、App、徽章、插画、头像场景生成,各种风格选择:科技风、运动风

汀丶人工智能

人工智能 AI绘画 MidJourney 文生图 prompt learning

Spring中@NotEmpty、@NotBlank、@NotNull 区别和使用

Java你猿哥

Java spring Spring Boot string ssm

企业级体验:未来体验管理的价值与趋势

博文视点Broadview

Springboot 一行代码实现文件上传 20个平台!少写代码到极致

Java你猿哥

Java spring Spring Boot ssm

腾讯Java大牛整理推荐的(Spring AOP/IOC思维导图源码笔记)

做梦都在改BUG

Java spring aop ioc

华为Atlas 200I DK A2开箱!

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号 5 月 PK 榜

骚操作:使用无头浏览器模拟用户操作进行截图~

王中阳Go

Go 高效工作 自动化 无头浏览器 自动截图

eKuiper 源码解读:从一条 SQL 到流处理任务的旅程

Java你猿哥

Go golang sql ssm eKuiper

OpenFeign 如何做到 "隔空取物" ?

Java你猿哥

Java Spring Cloud ssm netflix openfeign

用低代码开发平台高效打造仓储管理数字生态

力软低代码开发平台

降低 Spark 计算成本 50.18 %,使用 Kyligence 湖仓引擎构建云原生大数据底座,为计算提速 2x

Kyligence

开源 数据分析

牛客网最热门的1353道大厂Java面试题整理(附答案)

采菊东篱下

Java 面试

如何快速使用Redis可视化工具NineData?

数据库小组

数据库 Redis 可视化工具 Redis图形化工具 redis图形化界面 数据库可视化工具

OpenHarmony Docker移植实践

OpenHarmony开发者

OpenHarmony

一种DWS迁移Oracle的CONNECT BY语法的方案

华为云开发者联盟

数据库 华为云 华为云开发者联盟 企业号 5 月 PK 榜

浅谈微服务中限流熔断降级的方法论

做梦都在改BUG

Java 微服务 限流 熔断降级

低代码赋能生物药企数字化

明道云

Midjourney|文心一格prompt教程[Text Prompt(下篇)]:游戏、实物、人物、风景、动漫、邮票、海报等生成,终极模板教学

汀丶人工智能

人工智能 AI绘画 MidJourney 文生图 prompt learning

2023企业数智化财务创新峰会 · 成都站圆满举办!

用友BIP

智能会计 价值财务

开源赋能 普惠未来|TencentOS Tiny诚邀您参与2023开放原子全球开源峰会

开放原子开源基金会

三本菜鸟美团二面被源码暴锤,46天狂学Spring,终入阿里

Java你猿哥

面试 Spring Boot sprnig spring aop spring ioc

美团二面惜败,我的凉经复盘(附学习笔记+面试整理+进阶书籍)

Java你猿哥

MySQL redis Spring Boot 并发编程 JVm虚拟机

开源赋能 普惠未来|百度寄语2023开放原子全球开源峰会

开放原子开源基金会

宝武中南钢铁借助飞桨让钢筋超限监控有了“火眼金睛”

飞桨PaddlePaddle

百度飞桨 图像分割 PaddleSeg

阿里云微服务引擎 MSE 全新升级,实用能力更普惠,最高降幅 75%

阿里巴巴云原生

阿里云 云原生 微服务引擎

人工智能与大模型主题师资培训落地,飞桨持续赋能AI人才培养

飞桨PaddlePaddle

paddle 百度飞桨

阿里蚂蚁金服4面面经(已拿Offer)附答案!突如其来的意外之喜

Java你猿哥

Java 算法 ssm 并发 面经

二面蚂蚁金服(交叉面),已拿Offer,Java岗定级阿里P6

Java你猿哥

Java ssm 并发 java面试 面经

阿里巴巴最新SpringCloudAlibaba学习笔记,全程通俗易懂,一套搞懂!

架构师之道

微服务

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