Swift runtime

作者: just东东 | 来源:发表于2021-02-08 16:58 被阅读0次

    Swift runtime

    [TOC]

    前言

    我的另一篇文章关于Objective-CiOS Runtime简介,可以参考着阅读。

    我们都知道Objective-C是一门动态语言,其动态特性主要依赖于runtime。但是Swift是一门静态语言,那么我们常常说起的Swift runtime是怎么回事呢?其实纯Swift是没有动态性的,Swift runtime是指Swift中的属性或者方法在一些修饰符修饰,继承自NSObject的时候,可以使用runtime API进行相关的调用,下面我们就来详细的分析一下。

    1. Runtime API

    本章节不是介绍Runtime API的,而是通过Runtime API获取Swift类中的方法和属性。

    1.1 初步探索

    首先我们创建一个macOS命令行工程,语言选择Swift,然后在main.swift中定义一个Swift类,代码如下:

    class Teacher {
        var age: Int = 18
    
        func teach(){
            print("teach")
        }
    }
    

    然后编写一个test方法,在该方法中使用Runtime API去获取上面定义的类方法和属性列表。(注意在Swift中使用Runtime的时候是不需要import的)

    func test(){
        var methodCount:UInt32 = 0
        let methodlist = class_copyMethodList(Teacher.self, &methodCount)
        for  i in 0..<numericCast(methodCount) {
            if let method = methodlist?[i]{
                let methodName = method_getName(method);
                print("方法列表:\(String(describing: methodName))")
            }else{
                print("not found method");
            }
        }
        
        var count:UInt32 = 0
        let proList = class_copyPropertyList(Teacher.self, &count)
        for  i in 0..<numericCast(count) {
            if let property = proList?[i]{
                let propertyName = property_getName(property);
                print("属性列表:\(String(utf8String: propertyName)!)")
            }else{
                print("not found property");
            }
        }
        
        print("test run")
    }
    
    test()
    

    运行,打印结果:

    image

    根据打印结果我们可以知道,此时并没有打印任何方法和属性的列表。

    接下来我们修改一下我们的类:

    class Teacher {
        @objc var age: Int = 18
    
        @objc func teach(){
            print("teach")
        }
    }
    
    image

    此时我们可以看到即打印了方列表也打印了属性列表。但是在Swift 方法(函数)调度我们提到过,如果在Swift类中只是添加了@objc修饰并不能OC中被调用。如果需要在OC中调用还需要继承自NSObject,那么我们修改代码如下:

    class Teacher: NSObject {
        var age: Int = 18
    
        func teach(){
            print("teach")
        }
    }
    
    image

    此处只是打印了一个init方法,我们再次修改类

    class Teacher: NSObject {
        @objc var age: Int = 18
    
        @objc func teach(){
            print("teach")
        }
    }
    

    运行,打印结果:


    image

    此时我们可以看到我们想要的都打印了,其实为了在Swift方法中具有动态性,还可以给方法添加dynamic关键字进行修饰。打印结果与上面是一样的。关于方法的调度加了什么关键字会有不同的调度方式,具体还是去参考我的另一篇文章Swift 方法(函数)调度

    说了这些有什么用呢?其实从最开始的纯Swift类的什么都没打印到最后打印了方法列表和属性列表,我们正在一步一步的引入RuntimeSwift中怎么使用,以及其关联性。

    1.2 在objc源码工程中调试

    下面我们打开一份可编译的objc源码,没有的可以去LGCooci的GitHub下载objc4_debug。由于升级了Xcode此时我使用的是objc4-881.2

    新建一个SwiftTest Target,语言选择Swift,将刚才的代码粘贴过去。运行,查看打印结果与刚才的一致。

    这里将代码修改成纯Swift代码,然后跟一下源码,以获取方法列表为例,获取属性也差不多。首先搜索class_copyMethodList,在objc-runtime-new.mm文件中,添加如下断点。

    image

    注意:首先给我们的test方法中的class_copyMethodList那行代码添加断点,待执行到那里的时候在打开上图所示的断点。到上图所示的断点后,我们查看一下cls

    image

    然后点击data()跳转进去,在里面我们就可以看到objc_class的源码,这里的superclass就是指向父类的指针,cache是方法调度时的缓存,bits中通过rorw存储着方法和属性。关于这些可以参考我的其他关于Objective-C底层原理分析的文章。iOS OC 类原理

    这里我们就打印一下superclass,结果如下:

    image

    我们可以看到,在一个Swift类中,如果什么都不继承,它的默认基类是SwiftObject。返回class_copyMethodList函数中,我们看获取到的方法列表。

    image

    我们可以看到这个methods是个二维数组,至于里面为什么都是0呢?因为我修改类的时候没加@objc所以取不到,然后就都是0了。加上@objc就有值了。

    2. SwiftObject

    在上文中我们发现,在一个什么都不继承的Swift类中,默认继承的基类是SwiftObject,下面我们就去Swift源码中搜索一下。

    SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
     @private
      Class isa;
      SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
    }
    
    // The members of the HeapObject header that are not shared by a
    // standard Objective-C instance
    #define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
      InlineRefCounts refCounts
    

    SwiftObject.h文件中我们找到了上面的代码,我们可以看到这里是遵守了NSObject协议的。而且在Swift源码中也有一个TargetAnyClassMetadata有着部分与OCobjc_class类似的结构。

    image

    因为Swift是一门替代Objective-C的语音,所以有着与OC相似的底层也是很正常的,毕竟要慢慢过渡,两者之间互通是必要的过程。

    另外在objc源码中我们也可以看到很多Swift的身影,比如swift_class_t就是一个很典型的例子。继承自objc_class,又有着很多Swift特有的属性。

    struct swift_class_t : objc_class {
        uint32_t flags;
        uint32_t instanceAddressOffset;
        uint32_t instanceSize;
        uint16_t instanceAlignMask;
        uint16_t reserved;
    
        uint32_t classSize;
        uint32_t classAddressOffset;
        void *description;
        // ...
    
        void *baseAddress() {
            return (void *)((uint8_t *)this - classAddressOffset);
        }
    };
    

    那么我们不禁会有一个疑问,既然底层是能够互通的,那么为什么在OC中使用Swift类的时候还要集成自NSObject

    其实就是通过NSObject声明后,编译器才能判断当前的类是一个与OC交互的类。

    3. 总结

    关注Swift Runtime就分析这么多了,其实应用起来跟OC的差别也不大,下面做个简单的总结:

    1. 对于纯Swift来说是没有动态特性的,因为Swift是一门静态语言
    2. 方法和属性在不加任何修饰符的情况下不具备所谓的Runtime特性
    3. Swift中的方法调度主要是函数表调度V-Table
    4. 对于纯Swift类,我们给方法和属性添加@objc标识的情况下,可以通过Runtime API拿到方法和属性列表,但是还不能在OC中调度
    5. 对于继承自NSObject的类来说,如果想要动态的获取当前属性和方法,必须在其声明前添加@objc关键字
    6. 如果需要使用方法交换需要添加dynamic标识。如果不添加则不能通过Runtime API进行调用
    7. swift底层源码中有一个SwiftObject类有着与OC类似的底层结构,在objc源码中也有一个swift_class_t结构体,继承自objc_class,因此swiftoc能够有良好的交互

    相关文章

      网友评论

        本文标题:Swift runtime

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