Swift进阶04:方法调度

2021-02-15  本文已影响0人  黑白森林无间道

静态派发

值类型对象的函数的调用方式是静态调用,即直接地址调用,调用函数指针,这个函数指针在编译、链接完成之后就已经确定了,存放在代码段,而结构体内部并不存放方法。因此可以通过地址直接调用

静态派发 Mach-O文件

对于上面的分析,有个疑问:直接地址调用后面是符号,这个符号是怎么来的?

符号 image

还可以通过终端命令nm,获取项目中的符号表

函数符号命名规则

#include <stdio.h>
void test(){}
image

ASLR(随机地址偏移)

新建一个iOS项目,在 ViewController中定义一下代码

struct HTStack {
    func teacher() {
        print("teacher")
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let t = HTStack()
        t.teacher()
        print("end")
    }
}
ASLR image image image

动态派发

汇编指令补充

ARM64汇编指令

探索class的调度方式

首先介绍下V_Table在SIL文件中的格式

//声明sil vtable关键字
decl ::= sil-vtable
//sil vtable中包含 关键字、标识(即类名)、所有的方法
2 sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
//方法中包含了声明以及函数名称
3 sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-name

例如,以HTTacher为例,其SIL中的v-table如下所示

class HTTeacher {
    func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}
image

函数表源码探索

下面来进行函数表底层的源码探索

问题:如果更改方法声明的位置呢?例如 extension中的函数,此时的函数调度方式还是函数表调度吗?

extension HTTeacher {
    func teacher4() { print("teacher4") }
}
class HTStudent: HTTeacher {}

开发注意点:

final、@objc、dynamic修饰函数

final 修饰

class HTTeacher {
    final func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}
image

@objc 修饰

class HTTeacher {
    @objc func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}

【小技巧】混编头文件查看方式:查看项目名-Swift.h头文件

image
<!--swift类-->
class HTTeacher: NSObject {
    @objc func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}

<!--桥接文件中的声明-->
SWIFT_CLASS("_TtC11HTSwiftDemo9HTTeacher")
@interface HTTeacher : NSObject
- (void)teacher;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

查看 SIL文件发现被 @objc修饰的函数声明有两个:swift + OC(内部调用的swift中的teach函数)

image

即在SIL文件中生成了两个方法

dynamic 修饰

以下面代码为例,查看 dynamic修饰的函数的调度方式

class HTTeacher: NSObject {
    dynamic func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}

@objc + dynamic

class HTTeacher: NSObject {
    @objc dynamic func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}

swift中实现方法交换

在swift中的需要交换的函数前,使用dynamic修饰,然后通过: @_dynamicReplacement(for: 函数符号)进行交换,如下所示

class HTTeacher {
    dynamic func teacher() { print("teacher") }
    func teacher1() { print("teacher1") }
    func teacher2() { print("teacher2") }
    func teacher3() { print("teacher3") }
}

extension HTTeacher {
    @_dynamicReplacement(for: teacher)
    func teacher5() {
        print("teacher5")
    }
}

var t = HTTeacher()
t.teacher()

teacher()方法替换成了 teacher5

image

方法调度总结

内存插件 libfooplugin.dylib的使用

安装和使用

方式一

方式二

使用

内存分区调试实践

堆区

对于堆区的内存来说,就是通过 new & malloc 关键字来申请的内存空间,不连续,类似链表的结构,最直观的就是类的实例对象。
定义代码如下,通过cat查看类实例的内存分区

class HTTeahcer {
    func teacher() {
        print("teacher")
    }
}

var t = HTTeahcer()
image
从上图可以看出,类的实例对象存储在堆区,即 heap pointer

栈区

查看以下代码的 age内存地址位于哪个区?

func test() {
    // 我们在函数内部声明的age变量就是一个局部变量
    var age: Int = 18
    print(age)
}

test()
image
从结果来看,age位于栈区,即 stack address,此处的age如果用 let修饰,取不到地址

全局区

对于C的分析

下面是C语言的部分代码,查看其变量的内存地址

//全局已初始化变量
int a = 10;
//全局未初始化变量
int age;

//全局静态变量
static int age2 = 30;

int main(int argc, const char * argv[]) {
    
    char *p = "CJLTeacher";
    printf("%d", a);
    printf("%d", age2);
    return  0;
}
对于Swift的分析
let age = 10
var age2 = 20
image
总结
上一篇下一篇

猜你喜欢

热点阅读