Swift Runtime分析

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..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..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修饰,再试一把

Swift Runtime分析_第1张图片
打印结果

<---------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修饰(会编译错误)

你可能感兴趣的:(项目,Swifit)