专注iOS开发的小渣渣mstswift

swift底层探索 05 -深入探讨swift的方法调用机制

2020-12-21  本文已影响0人  Henry________

swift底层探索 03 - 值类型、引用类型一文中解释过值类型和引用类型的内存布局。像这样:

图一

1. struct-直接调用

要想知道方法是如何调用的,我是从方法是如何保存开始探索。

从[图一]看结构体没有像那样的继承、metaData-ISA逻辑。而内存中也找不到方法的任何踪迹,方法存在哪里呢?

1.1 struct方法调用

调用方法structfunc1,然后打开汇编堆栈


图二

machOView查看

图三 图四

1.2 struct + protocol

protocol Prot {
    func protocolFunc()
}

struct structModel:Prot{
    var age : Int = 18
    func structfunc1(){}
    func structfunc2(){}
    
    func protocolFunc(){}
}
var str = structModel()
str.protocolFunc()

同样打开汇编调试


1.3 struct + extension情况

struct structModel{
    var age : Int = 18
    func structfunc1(){}
    func structfunc2(){}
}

extension structModel{
    func extensionFunc(){}
}
var str = structModel()
str.extensionFunc()

打开汇编调试


【总结】

  1. 结构体的方法无需额外空间存储,在编译链接阶段就已经确定,编译器在你调用之前就已经确定了方法的指针地址,这种方法调用称为直接调用

2. enum-直接调用

enum enumModel{
    case `default`
    
    func enumFunc(){}
}
enumModel.default.enumFunc()

打开汇编调试


【总结】

  1. 值类型直接调用

3. class-方法调用(函数表调用)

3.1 普通方法

借助之前的经验,先使用汇编来看一下调用堆栈。

class classtModel{
    func classfunc1(){}
    func classfunc2(){}
}
var cls = classtModel()
cls.classfunc1()
cls.classfunc2()

汇编调用堆栈


图五

sil文件


图六

通过图五,图六得出的结论:

swift函数表初始化源码

通过汇编的查看知道了方法和类本身的关系的,方法是如何存储的呢?


  1. 代码准备
class PersonModel{
   var age : Int = 18
   func test(){}
   func test1(){}
}

var p = PersonModel()
        p.test()
        p.test1()
调试验证
  1. 代码验证,编译并打开汇编调试
class PersonModel{
   var age : Int = 18
   func test(){}
   func test1(){}
}
  var p = PersonModel()
  p.test()
  p.test1()
  1. 读取寄存期


  2. 结论


swift中vtable与oc中method_list区别

oc-method_list


swift-vtable

class superClass{
    func superClassfunc1(){}
}
class classtModel:superClass{
    func classfunc1(){}
    func classfunc2(){}
}
extension classtModel{
    func extensionfunc1(){}
}

看下sil文件


3.2 class + protocol

protocol Prot {
    func protocolFunc()
}
class classtModel:Prot{
    func classfunc1(){}
    func classfunc2(){}
    func protocolFunc(){}
}

查看sil文件


3.3 class + entension

class classtModel{
    func classfunc1(){}
}
extension classtModel{
    func extensionfunc1(){}
}
var cls = classtModel()
cls.extensionfunc1()

打开汇编调试


3.4 final、class、static关键字

方法前增加final关键字后,该方法不得被继承。

class classtModel{
    final func classfunc1(){}
}
var cls = classtModel()
cls.classfunc1()

打开汇编调试


注:
增加class关键字static关键字后,调用方式都会变为直接调用

3.5 @objc / dynamic关键字

@objc是将该方法暴露给oc使用
dynamic关键字是将方法标记为可变方法。

以@objc为例:

class classtModel{
    @objc func classfunc1(){}
    dynamic func classfunc2(){}
}

查看sil文件:


3.6 @objc dynamic 组合关键字

@objc dynamic是将方法保留给oc且还可以动态修改

class classtModel{
    @objc dynamic func classfunc2(){  }
}
var cls = classtModel()
cls.classfunc2()

【面试题】

protocol TestProtocol {
    
}
class CJLTeacher: TestProtocol{
    var age = 18
    var double = 1.85
    func teach(){
        print("LGTeacher teach")
    }
}

func test(_ value: TestProtocol){
    let valueType = type(of: value)
    print(valueType)
}
var t = CJLTeacher()
let t1: TestProtocol = CJLTeacher()
test(t)
test(t1)

会输出什么?

LGTeacher teach
TestProtocol teach

如果是这样

protocol TestProtocol {
    func teach(){
        print("TestProtocol teach")
    }
}

输出又会变成:

LGTeacher teach
LGTeacher teach

【总结】

引用类型的方法调用相对复杂:直接调用,函数表调用,消息转发都会出现。

调用方式总结

值类型 引用类型
普通方法 直接调用 函数表调用
protocol协议 直接调用 函数表调用
extension拓展 直接调用 直接调用
final - 直接调用
继承方法 - 函数表调用
@objc - 函数表调用
dynamic - 函数表调用
@objc dynamic - objc_msgSend消息转发

dynamic拓展

class classtModel{
    dynamic func classfunc2(){
        print("classfunc2")
    }
}
extension classtModel{
    @_dynamicReplacement(for: classfunc2)
    private func repFunc(){
        print("repFunc")
    }
}
var cls = classtModel()
cls.classfunc2()
上一篇下一篇

猜你喜欢

热点阅读