博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swift Runtime动态性分析
阅读量:6068 次
发布时间:2019-06-20

本文共 3987 字,大约阅读时间需要 13 分钟。

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(\"\\");\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 特性可以知道,所有运行时方法都依赖,也就是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) -\u0026gt; ()有个@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=\u0026lt;TestSwift.TestSwiftVC: 0x7fb4e1d148f0\u0026gt;\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中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博(,),微信(微信号:)关注我们。

转载地址:http://oafgx.baihongyu.com/

你可能感兴趣的文章
数据管理DMS 全量SQL诊断:你的SQL是健康的蓝色,还是危险的红色?
查看>>
搭建一个通用的脚手架
查看>>
开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
查看>>
开源磁盘加密软件VeraCrypt教程
查看>>
本地vs云:大数据厮杀的最终幸存者会是谁?
查看>>
阿里云公共镜像、自定义镜像、共享镜像和镜像市场的区别 ...
查看>>
shadowtunnel v1.7 发布:新增上级负载均衡支持独立密码
查看>>
Java线程:什么是线程
查看>>
mysql5.7 创建一个超级管理员
查看>>
【框架整合】Maven-SpringMVC3.X+Spring3.X+MyBatis3-日志、JSON解析、表关联查询等均已配置好...
查看>>
要想成为高级Java程序员需要具备哪些知识呢?
查看>>
带着问题去学习--Nginx配置解析(一)
查看>>
onix-文件系统
查看>>
java.io.Serializable浅析
查看>>
我的友情链接
查看>>
多线程之线程池任务管理通用模板
查看>>
CSS3让长单词与URL地址自动换行——word-wrap属性
查看>>
CodeForces 580B Kefa and Company
查看>>
开发规范浅谈
查看>>
Spark Streaming揭秘 Day29 深入理解Spark2.x中的Structured Streaming
查看>>