Swift Runtime分析
来源:互联网 发布:内圆弧编程实例 编辑:程序博客网 时间:2024/06/08 17:16
1、动态获取类的属性和方法
代码如下:
定义两个类SwifitClassA、SwifitClassB让他们尽量多的包含Swifit类型(Character、String、AnyObject、Tuple)
class SwifitClassA { var aBool: Bool = true var aInt: UInt = 0 var aFloat: Float = 23.45 var aDouble: Double = 455.654534 var aString: String = "string" var aObject: AnyObject! = nil //使用dynamic/@objc修饰就可以使用OC的runtime特性 func testReturnVoidWithaId(aId:UIView) { } //不能使用dynamic/@objc修饰,返回值为OC不识别的元组类型 func testReturnCharactor() -> (Int, Bool) { let aInt:Int = 4 let aBool:Bool = true return (aInt, aBool) } //不能使用dynamic/@objc修饰有OC不识别的类型Character func testReturnVoidWithCharacter(aCharacter:Character) { }}class SwifitClassB:UIViewController { var aBool: Bool = true var aInt: UInt = 0 var aFloat: Float = 23.45 var aDouble: Double = 455.654534 var aString: String = "string" var aObject: AnyObject! = nil override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) } func testReturnVoidWithaId(aId:UIView) { } func testReturnVoidWithaBool(aBOOl:Bool, aInt:UInt, aFloat:Float, aDouble:Double, aString:String, aObject:AnyObject) { } //runTime不能获取有OC不识别的元组类型 func testReturnTuple(aBool:Bool, aInt:Int, afloat:Float) -> (Bool, Int, Float) { return (aBool, aInt, aFloat) } //runTime不能获取有OC不识别的Character类型 func testReturnVoidWithCharactor(aCharactor:Character) { } func tableview(table:UITableView, numberOfRowsInSection section:Int) -> Int { return 20 }}
动态获取SwifitClassA、SwifitClassB的属性和方法的实现
func showClassRunTime(cls:AnyClass) { print("<---------start methodList(\(cls))--------->") var methodNum:UInt32 = 0 let methodList = class_copyMethodList(cls, &methodNum) for index in 0..<methodNum { let method:Method = methodList[Int(index)] print(String(UTF8String: method_getTypeEncoding(method))) print(String(UTF8String: method_copyReturnType(method))) print(String(_sel:method_getName(method))) print("----") } print("<---------end methodList(\(cls))--------->") print("\n") print("<---------start propertyList(\(cls))--------->") var propertyNum:UInt32 = 0 let propertyList = class_copyPropertyList(cls, &propertyNum) for index in 0..<propertyNum { let property:objc_property_t = propertyList[Int(index)] print(String(UTF8String: property_getName(property))) print(String(UTF8String: property_getAttributes(property))) print("----") } print("<---------end propertyList(\(cls))--------->") }
方法调用
let swifitClassA: SwifitClassA = SwifitClassA()let swifitClassB: SwifitClassB = SwifitClassB()showClassRunTime(object_getClass(swifitClassA))print("\n==============================================\n")showClassRunTime(object_getClass(swifitClassB))
OK,让我们来看看结果是什么
<---------start methodList(SwifitClassA)---------><---------end methodList(SwifitClassA)---------><---------start propertyList(SwifitClassA)---------><---------end propertyList(SwifitClassA)--------->==============================================<---------start methodList(SwifitClassB)--------->Optional("B16@0:8") Optional("B")aBool----Optional("v20@0:8B16") Optional("v")setABool:----Optional("Q16@0:8") Optional("Q")aInt----Optional("v24@0:8Q16") Optional("v")setAInt:----Optional("f16@0:8") Optional("f")aFloat----Optional("v20@0:8f16") Optional("v")setAFloat:----Optional("d16@0:8") Optional("d")aDouble----Optional("v24@0:8d16")Optional("v")setADouble:----Optional("@16@0:8") Optional("@")aString----Optional("v24@0:8@16") Optional("v")setAString:----Optional("@16@0:8") Optional("@")aObject----Optional("v24@0:8@16") Optional("v")setAObject:----Optional("v24@0:8@16") Optional("v")testReturnVoidWithaId:----Optional("v56@0:8B16Q20f28d32@40@48") Optional("v")testReturnVoidWithaBool:aInt:aFloat:aDouble:aString:aObject:----Optional("q32@0:8@16q24") Optional("q")tableview:numberOfRowsInSection:----Optional("@32@0:8@16@24") Optional("@")initWithNibName:bundle:----Optional("v16@0:8") Optional("v")viewDidLoad----Optional("v20@0:8B16") Optional("v")viewDidAppear:----Optional("@?") Optional("@?").cxx_destruct----Optional("@24@0:8@16") Optional("@")initWithCoder:----<---------end methodList(SwifitClassB)---------><---------start propertyList(SwifitClassB)--------->Optional("aBool") Optional("TB,N,VaBool")----Optional("aInt") Optional("TQ,N,VaInt")----Optional("aFloat") Optional("Tf,N,VaFloat")----Optional("aDouble") Optional("Td,N,VaDouble")----Optional("aString") Optional("T@\"NSString\",N,C,VaString")----Optional("aObject") Optional("T@,N,&,VaObject")----<---------end propertyList(SwifitClassB)--------->
观察输出结果:
- 对于纯Swift的SwifitClassA来说任何方法、属性都未获取到。
- 对于SwifitClassB来说除testReturnTuple、testReturnVoidWithaCharacter两个方法外,其他的都获取成功了。
这是为什么?
纯Swift类的函数调用已经不再是Objective-c的运行时发消息,而是类似C++的vtable,在编译时就确定了调用哪个函数,所以没法通过runtime获取方法、属性。
SwifitClassB继承自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获取了。
2、Method Swizzling
动态性最常用的就是方法替换(Method Swizzling),将类的某个方法替换成自定义的方法。
对于纯Swift类(如TestASwiftClass)来说,无法通过objc runtime替换方法,因为由上面的测试可知拿不到这些方法、属性
对于继承自NSObject类(如TestSwiftVC)来说,无法通过runtime获取到的方法肯定没法替换了。那能通过runtime获取到的方法就都能被替换吗?我们测一把。
代码如下:
我们替换两个可以被runtime获取到的方法:viewDidAppear和testReturnVoidWithaId
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. methodSwizzing(object_getClass(self), originalSelector: Selector("viewDidAppear:"), swizzingSelector: Selector("sz_viewDidAppear:")) methodSwizzing(object_getClass(self), originalSelector: Selector("testReturnVoidWithId:"), swizzingSelector: Selector("sz_testReturnVoidWithId:")) } func methodSwizzing(cls:AnyClass, originalSelector:Selector, swizzingSelector:Selector) { let originalMethod = class_getInstanceMethod(cls, originalSelector) let swizzingMethod = class_getInstanceMethod(cls, swizzingSelector) let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzingMethod), method_getTypeEncoding(swizzingMethod)) if didAddMethod { class_replaceMethod(cls, swizzingSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)) }else { method_exchangeImplementations(originalMethod, swizzingMethod) } } override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) print("F:\(__FUNCTION__) L:\(__LINE__)") } func sz_viewDidAppear(animated: Bool) { super.viewDidAppear(animated) print("F:\(__FUNCTION__) L:\(__LINE__)") } func testReturnVoidWithId(aId:UIView) { print("F:\(__FUNCTION__) L:\(__LINE__)") } func sz_testReturnVoidWithId(aId:UIView) { print("F:\(__FUNCTION__) L:\(__LINE__)") }
输出结果如下
说明viewDidAppear已经被替换,但是testReturnVoidWithaId却没有被替换,这是为何?
查阅资料发现需要在属性和方法前面加上@obje或dynamic才能被动态替换
我们在方法前加上@objc测试一把试试
@objc func testReturnVoidWithId(aId:UIView) { print("F:\(__FUNCTION__) L:\(__LINE__)") } @objc func sz_testReturnVoidWithId(aId:UIView) { print("F:\(__FUNCTION__) L:\(__LINE__)") }
输出结果:
哦?好像是没起作用,why?
文档中有解释:
- 加了@objc标识的方法、属性无法保证都会被运行时调用,因为Swift会做静态优化。要想完全被动态调用,必须使用dynamic修饰。使用dynamic修饰将会隐式的加上@objc标识。
这也就解释了为什么testReturnVoidWithaId无法被替换,因为写在Swift里的代码直接被编译优化成静态调用了。
我们再试试dynamic
dynamic func testReturnVoidWithId(aId:UIView) { print("F:\(__FUNCTION__) L:\(__LINE__)") } dynamic func sz_testReturnVoidWithId(aId:UIView) { print("F:\(__FUNCTION__) L:\(__LINE__)") }
输出结果
OK,已经可以替换了
我们再把之前SwifitClassA的属性和方法使用@objc/dynamic修饰,再试一把
打印结果
<---------start methodList(SwifitClassA)--------->Optional("B16@0:8")Optional("B")aBool----Optional("v20@0:8B16")Optional("v")setABool:----Optional("Q16@0:8")Optional("Q")aInt----Optional("v24@0:8Q16")Optional("v")setAInt:----Optional("f16@0:8")Optional("f")aFloat----Optional("v20@0:8f16")Optional("v")setAFloat:----Optional("d16@0:8")Optional("d")aDouble----Optional("v24@0:8d16")Optional("v")setADouble:----Optional("@16@0:8")Optional("@")aString----Optional("v24@0:8@16")Optional("v")setAString:----Optional("@16@0:8")Optional("@")aObject----Optional("v24@0:8@16")Optional("v")setAObject:----Optional("v24@0:8@16")Optional("v")testReturnVoidWithaId:----<---------end methodList(SwifitClassA)---------><---------start propertyList(SwifitClassA)--------->Optional("aBool") Optional("TB,N,VaBool")----Optional("aInt") Optional("TQ,N,VaInt")----Optional("aFloat") Optional("Tf,N,VaFloat")----Optional("aDouble") Optional("Td,N,VaDouble")----Optional("aString") Optional("T@\"NSString\",N,C,VaString")----Optional("aObject") Optional("T@,N,&,VaObject")----<---------end propertyList(SwifitClassA)--------->
可以发现SwifitClassA的属性和方法也已经可以获取了
总结
纯Swift类没有动态性,但在方法、属性前添加dynamic修饰可以获得动态性。
继承自NSObject的Swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性。
若方法的参数、属性类型为Swift特有、无法映射到Objective-C的类型(如Character、Tuple),则此方法、属性无法添加dynamic修饰(会编译错误)
- Swift Runtime分析
- 【Swift】Runtime动态性分析
- Swift Runtime动态性分析
- Swift Runtime分析:还像OC Runtime一样吗?
- Swift runtime
- swift runtime type
- Swift 中的 Runtime
- Swift & the Objective-C Runtime
- Swift 调用 objc/runtime OBJC_ASSOCIATION_RETAIN
- [swift学习之十六]RunTime练习一
- [swift学习之十七]RunTime练习二
- Swift/Objc的Runtime(运行时)机制
- Swift 中使用runtime交换方法实现
- Swift KVO触发问题(runtime支持不友好)
- Swift防止按钮重复点击实现+Swift如何运用Runtime
- ThinkPHP框架的runtime分析
- 全方位分析Objcetive-C Runtime
- runLoop和runtime的分析
- 数字转换成字符串,int 转换成const char*
- 谈谈eclipse使用技巧二
- 解决oracle数据库中空表的导出
- C pirmer Plus(第五版) 第十四章 课后习题 3
- java集合类Stack类
- Swift Runtime分析
- iOS开发中UITextField限制输入文字个数
- JAVA的StringBuffer类
- C语言 计算a-|b|,aj减b的绝对值
- OJ
- centos 启动脚本
- 《Linux内核设计与实现》读书笔记(十四)- 块I/O层
- js使用AjaxFileupload插件实现文件上传
- 树莓派瞎玩~8~远程连接图形界面