美文网首页swift mvvm
Swift底层进阶--007:Runtime & 元类型

Swift底层进阶--007:Runtime & 元类型

作者: 帅驼驼 | 来源:发表于2020-12-31 18:32 被阅读0次
    Runtime探索
    案例1

    测试以下代码,能否将⽅法列表及成员属性打印出来?

    class LGTeacher {
        var age: Int = 18
        func teach(){
            print("teach")
        }
    }
    
    let t = LGTeacher()
    
    func test(){
        
        var methodCount: UInt32 = 0
        let methodList = class_copyMethodList(LGTeacher.self, &methodCount)
        
        for i in 0..<numericCast(methodCount) {
            if let method = methodList?[i]{
                let methodName = method_getName(method)
                print("方法列表:\(methodName)")
            }else{
                print("not found method")
            }
        }
        
        var count: UInt32 = 0
        let proList = class_copyPropertyList(LGTeacher.self, &count)
        
        for i in 0..<numericCast(count) {
            if let property = proList?[i]{
                let propertyName = property_getName(property)
                print("成员属性:\(property)")
            }else{
                print("not found property")
            }
        }
        
        print("test run")
    }
    
    test()
    
    //输出以下内容:
    //test run
    

    从运行结果来看并没有达到预期,⽅法列表和成员属性都没有打印出来

    案例2

    修改案例1,给方法和属性添加@objc修饰,能否打印出结果?

    class LGTeacher {
        @objc var age: Int = 18
        @objc func teach(){
            print("teach")
        }
    }
    
    //输出以下内容:
    //方法列表:teach
    //方法列表:age
    //方法列表:setAge:
    //成员属性:0x0000000100008510
    //test run
    

    从运行结果来看,⽅法列表及成员属性全部被打印出来,但Class没有继承NSObject,所以并不能暴漏给OC使用

    案例3

    修改案例2,将Class继承于NSObject,去掉@objc修饰,能否打印出结果?

    class LGTeacher : NSObject {
        var age: Int = 18
        func teach(){
            print("teach")
        }
    }
    
    //输出以下内容:
    //方法列表:init
    //test run
    

    从运行结果来看,只有init方法被打印出来。因为继承NSObject后,swift.h中默认只有init方法暴露

    案例4

    修改案例3Class继承于NSObject,同时给方法和属性添加@objc修饰,能否打印出结果?

    class LGTeacher : NSObject {
        @objc var age: Int = 18
        @objc func teach(){
            print("teach")
        }
    }
    
    //输出以下内容:
    //方法列表:teach
    //方法列表:init
    //方法列表:age
    //方法列表:setAge:
    //成员属性:0x0000000100008518
    

    从运行结果来看,⽅法列表及成员属性全部被打印出来,同时可供OC使用,但对于teach()方法,依然是V_table函数表调度,无法使用Runtime的方法交换,因为方法此时还不具备动态特性

    案例5

    修改案例4Class继承于NSObject,将@objc修饰改为dynamic修饰,能否打印出结果?

    class LGTeacher : NSObject {
        dynamic var age: Int = 18
        dynamic func teach(){
            print("teach")
        }
    }
    
    //输出以下内容:
    //方法列表:init
    //test run
    

    从运行结果来看,还是只有init方法被打印出来。因为dynamic修饰只给方法和属性增加了动态特性,它们依然不能被OC使用

    通过上述案例,得出以下结论:

    • Swift是静态语言,所以没有动态特性。⽅法和属性不加任何修饰符的情况下,不具备所谓的Runtime特性,它的方法调度方式使用V_table函数表调度
    • 对于纯Swift类,给⽅法和属性添加@objc修饰后,可以通过Runtime API获取到⽅法和属性列表,但是在OC中无法进⾏调度,例如Runtime的方法交换
    • 继承⾃NSObject的类,如果想要动态获取当前⽅法和属性,必须在其声明前添加@objc关键字。如果想通过Runtime API使用它们,例如Runtime的方法交换,需要添加dynamic关键字,让它们具备动态特性
    objc源码分析

    进入class_copyMethodList定义,先获取当前的data,而data的作用就是存储类的信息

    class_copyMethodList

    进入data定义,在objc_class里打印superclass,输出的是Swift中有默认基类_SwiftObject

    data

    Swift源码中找到_SwiftObject,发现它实现了NSObject协议。本质上Swift为了和OC进行交互,它保留了OC的数据结构

    _SwiftObject

    回到objc源码,打印methods,输出一个存放method的二维数组。使用@objc修饰的方法就能被获取到,因为Swift在底层数据结构和OC保持部分一致

    methods

    objc源码中找到swift_class_t,继承自objc_class,保留了父类isasuperclasscacheDatadata四个属性,其次才是自己的属性

    swift_class_t
    必须继承NSObject的原因:Swift在底层数据结构和OC只保持了部分一致,通过NSObject的声明,标记了当前类是一个和OC交互的类。可以帮助编译器判断这个类在编译过程中,到底应该走哪些方法的分支。因为上层API的调用者暴露不同,所以选择不同,在编译器里优化的调用方式也就不同。
    元类型
    AnyObject

    代表任意类的instance,类的类型,仅类遵守的协议

    class LGTeacher {
        var age: Int = 18
    }
    
    var t = LGTeacher()
    
    //此时代表的就是当前 LGTeacher 的实例对象
    var t1: AnyObject = t
    
    //此时代表的就是 LGTeacher 这个类的类型
    var t2: AnyObject = LGTeacher.self
    
    //仅类遵守的协议
    protocol JSONMap: AnyObject {}
    
    //class可遵守此协议
    class LGJSONMap: JSONMap {}
    

    上述代码中,t1代表LGTeacher的实例对象,t2代表LGTeacher类的类型,JSONMap是仅类遵守的协议,因为LGJSONMapClass类型,所以可以遵守JSONMap协议

    如果是结构体,可以遵守JSONMap协议吗?

    编译报错 JSONMap是仅类遵守的协议,结构体无法使用,编译报错
    Any

    代表任意类型,包括funcation类型或者Optional类型

    var array: [Any] = [1, "Teacher", true]
    

    Any包含的类型比AnyObject更为广泛,可以理解为AnyObjectAny的子集。

    如果将array数组的元素声明为AnyObject类型,编译报错

    编译报错
    AnyClass

    代表任意实例的类型:AnyObject.Type

    public typealias AnyClass = AnyObject.Type
    

    上述代码是AnyClass的定义,类型是AnyObject.Type

    T.self
    • T是实例对象,返回的就是它本身
    • T是类,那么返回的是Metadata
    class LGTeacher {
        var age: Int = 18
    }
    
    var t = LGTeacher()
    //返回实例对象本身
    var t1 = t.self
    //返回LGTeacher这个类的类型,metadata元类型
    var t2 = LGTeacher.self
    

    上述代码中, t1返回的是实例对象本身, t2返回的是LGTeacher.Type,也就是LGTeacher这个类的类型

    T.Type

    ⼀种类型,T.self的类型是T.Type

    T.Type
    type(of:)

    ⽤来获取⼀个值的动态类型

    var age: Int = 18
    
    func test(_ value : Any) {
        print(type(of: value))
    }
    
    test(age)
    
    //输出以下内容:
    //Int
    

    上述代码中,value的静态类型(static type)Any,是编译期确定好的。而type(of:)方法⽤来获取⼀个值的动态类型(dynamic type),所以输出的是Int

    案例1

    valueLGTeacher类型,实际传入的tLGChild类型,在test方法内执行value.teahc(),打印结果是什么?

    class LGTeacher {
        func teahc() {
            print("LGTeacher teahc")
        }
    }
    
    class LGChild : LGTeacher {
        override func teahc() {
            print("LGChild teahc")
        }
    }
    
    func test(_ value : LGTeacher) {
        value.teahc();
    }
    
    var t = LGChild()
    test(t)
    
    //输出以下内容:
    //LGChild teahc
    

    上述代码中,value编译期类型是LGTeacher,运行时的实际类型是LGChild,所以打印结果是LGChild teahc

    案例2

    valueTestProtocol类型,分别传入t1t2,打印结果是什么?

    protocol TestProtocol {}
    
    class LGTeacher : TestProtocol {}
    
    func test(_ value : TestProtocol) {
        print(type(of: value))
    }
    
    var t1 = LGTeacher()
    var t2: TestProtocol = LGTeacher()
    
    test(t1)
    test(t2)
    
    //输出以下内容:
    //LGTeacher
    //LGTeacher
    

    上述代码中,分别传入的t1t2,运行时的实际类型都是LGTeacher,所以打印的type(of:)都是LGTeacher

    案例3

    value是泛型,分别传入t1t2,打印结果是什么?

    protocol TestProtocol {}
    
    class LGTeacher: TestProtocol {}
    
    func test<T>(_ value : T) {
        print(type(of: value))
    }
    
    var t1 = LGTeacher()
    var t2: TestProtocol = LGTeacher()
    
    test(t1)
    test(t2)
    
    //输出以下内容:
    //LGTeacher
    //TestProtocol
    

    上述代码中,test方法的参数改为泛型,两次打印type(of:)的结果不一样了,t1输出LGTeachert2输出TestProtocol。因为当泛型和协议同时参与,编译器无法推导出准确类型,需要在调用type(of:)时将value转换为Any

    func test<T>(_ value : T) {
        print(type(of: value as Any))
    }
    
    //输出以下内容:
    //LGTeacher
    //LGTeacher
    

    修改后的代码,打印结果都是LGTeacher

    案例4

    class_getClassMethod方法cls参数要求传入AnyClass类型,如果传入t.self,可以获取方法列表吗?

    编译报错 如图所示,因为t是实例对象,t.self返回的就是它本身,是LGTeacher类型。而参数cls要求传入AnyClass类型,也就是需要LGTeacher.Type类型,所以类型不符,编译报错

    相关文章

      网友评论

        本文标题:Swift底层进阶--007:Runtime & 元类型

        本文链接:https://www.haomeiwen.com/subject/jvvhoktx.html