在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。Swift已经开源,目前最新版本为2.2。我们知道Objective-C是具有动态性的,能够通过runtime API调用和替换任意方法,那Swift也具有这些动态性吗?\ 分析用例\我们拿一个 代码如下: (点击放大图像) \\\方法、属性\动态性比较重要的一点就是能够拿到某个类所有的方法、属性,我们使用如下代码来打印方法和属性列表。 (点击放大图像) \\\调用showClsRuntime的代码如下: let aSwiftClass:TestASwiftClass = TestASwiftClass();\showClsRuntime(object_getClass(aSwiftClass));\print(\"\\");\showClsRuntime(object_getClass(self));\ 看看我们得到什么结果?\ (点击放大图像) \\* 对于纯Swift的TestASwiftClass来说任何方法、属性都未获取到 * 对于TestSwiftVC来说除
这是为什么?\
但为什么
从Objective-c的runtime 特性可以知道,所有运行时方法都依赖TypeEncoding,也就是method_getTypeEncoding返回的结果,他指定了方法的参数类型以及在函数调用时参数入栈所要的内存空间,没有这个标识就无法动态的压入参数(比如testReturnVoidWithaId: Optional(\"[email protected]:[email protected]\") Optional(\"v\"),表示此方法参数共需24个字节,返回值为void,第一个参数为id,第二个为selector,第三个为id),而Character和Tuple是Swift特有的,无法映射到OC的类型,更无法用OC的typeEncoding表示,也就没法通过runtime获取了。\ Method Swizzling\动态性最常用的就是方法替换(Method Swizzling),将类的某个方法替换成自定义的方法,从而达到hook的作用。\
(点击放大图像) \\我们替换两个可以被runtime获取到的方法: (点击放大图像) \\打印的日志为 F:testReturnVoidWithaId L:50\F:sz_viewDidAppear L:46\ 说明viewDidAppear已经被替换,但是testReturnVoidWithaId却没有被替换,这是为何?\ 我们在方法里打个断点看看,如图: (点击放大图像) \\(点击放大图像) \\可以看到区别,调用sz_viewDidAppear栈的前一帧为
@objc\找到官方文档读读。 可以知道@objc是用来将Swift的API导出给Objective-C和Objective-C runtime使用的,如果你的类继承自Objective-c的类(如NSObject)将会自动被编译器插入@objc标识。 我们在把TestASwiftClass(纯Swift类)的方法、属性前都加个@objc 试试,如图: (点击放大图像) \\查看日志可以发现加了@objc的方法、属性均可以被runtime获取到了。 (点击放大图像) \\\dynamic\文档里还有一句说明:
这也就解释了为什么testReturnVoidWithaId无法被替换,因为写在Swift里的代码直接被编译优化成静态调用了。 而viewDidAppear是继承Objective-C类获得的方法,本身就被修饰为dynamic,所以能被动态替换。 我们把TestSwiftVC方法前加上dynamic再测一把,如图: (点击放大图像) \\从堆栈也可以看出,方法的调用前增加了@objc标识,testReturnVoidWithaId方法被替换成功了。\ 同样的做法,我们把TestASwiftClass的方法和属性也都加上dynamic修饰,做Method Swizzling,同样获得成功,如图 (点击放大图像) \\\Objective-C获取Swift runtime信息\在Objective-c代码里使用 我们初始化一个对象,并断点和打印看看,如下图: (点击放大图像) \\可以看到Swift中的TestSwiftVC类在OC中的类名已经变成 所以要想从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的 (点击放大图像) \\运行之后我们得到结果 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也会打印日志,为什么?留给读者思考)\ 总结\
本文作者\尹峥伟(花名 君展),来自手机淘宝技术团队的资深无线开发工程师,主要负责手机淘宝基础架构研发,github开源库Wax的维护者,微信号yzwlvzxh,微博@君展。\\\ 感谢徐川对本文的审校。 \给InfoQ中文站投稿或者参与内容翻译工作,请邮件至[email protected]。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号:InfoQChina)关注我们。 |
请发表评论