iOS 开发每天分享优质文章

Swift runtime

2021-02-08  本文已影响0人  just东东

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能够有良好的交互
上一篇下一篇

猜你喜欢

热点阅读