swift方法调度总结

2022-09-28 13:50:04 浏览数 (1)

方法调度

结论
  • Class中的方法
    • public open internal 方法调度都是函数派发方式
    • private fileprivate final 方法调度为静态派发方式
    • extension 中的方法都为静态派发方式
  • Struct中的方法
    • 全部都是静态派发调度方式: mutating extension public private...
  • Protocol中的方法
    • 方法最初定义在协议本身内, 则方法以协议函数表的方式调度
    • 方法最初定义在协议延展内, 则方法以静态派发的方式调度
验证Class中的方法调度

1、创建ClassPerson.swift原始文件。

代码语言:javascript复制
class ClassPerson: NSObject {

    override init() {
        super.init()
        personFuncName1()
        personFuncName2()
        personFuncName3()
        personFuncName4()
        personFuncName5()
        personFuncName6()
        personFuncName7()
        personFuncName8()
    }
    
    dynamic func teach() {
        debugPrint(#function)
    }
    
    /// 消除函数调用后返回值未被使用的警告⚠
    /// 以前写法防止警告: _ = resultTest()
    @discardableResult func resultTest() -> Bool {
        return false
    }
    
    /// 函数表派发方式
    func personFuncName1() {
    }
    
    /// 函数表派发方式
    func personFuncName2() {
    }
    
    /// 加了 private 则为静态派发
    private func personFuncName5() {
    }
    /// 加了 final 则为静态派发
    final func personFuncName6() {
    }
    
    ///@objc dynamic 消息转发msgSend方式
    @objc dynamic func personFuncName7() {
    }
    
    @objc func personFuncName8() {
    }
}

/// 扩展里的方法都是静态派发方式
extension ClassPerson {
    /// swift5 方法替换 需要对被替换的方法加dynamic修饰
    @_dynamicReplacement(for: teach())
    private func teach1() {
        debugPrint(#function)
    }
    
    func personFuncName3() {
    }
    
    func personFuncName4() {
    }
}

2、编译sil文件 从终端进入到ClassPerson.swift目录下,在同级目录下生成sil文件。

代码语言:javascript复制
// 编译 sil
swiftc -emit-sil Person.swift >> Person.sil
// 编译成带转译的 sil
swiftc -emit-sil Person.swift | xcrun swift-demangle >> Person.sil
// 编译成带转译的 ir
swiftc -emit-ir Person.swift | xcrun swift-demangle >> Person.ll

//其它
生成语法树: swiftc -dump-ast main.swift
生成最简洁的SIL中间代码:swiftc -emit-sil main.swift
生成LLVM的IR代码:swiftc -emit-ir main.swift -o main.ll
生成汇编代码:swiftc -emit-assembly main.swift -o main.s

转义后的sil文件能清晰的看出方法调用。

转义sil文件.png

如果不转义sil能否确定这就是personFuncName4()方法呢,使用下面命令行:

代码语言:javascript复制
xcrun swift-demangle <混写后的名称>

real_function_name.png

function_ref

找到init初始化方法中对其它方法的调用。其中带有function_ref的就是静态派发调度方式。

function_ref.png

  • personFuncName3 personFuncName4 是扩展方法
  • personFuncName5private修饰的方法
  • personFuncName6final 修饰的方法

以上三种情况定义的方法都是静态派发调度方式。

断点汇编查看

xcode顶部导航栏选择Debug->Debug Workflow->Always Show Disassemebly,在init()方法最后打个断点,运行程序:

function_ref1.png

从汇编调试中明显看出方法personFuncName3 personFuncName4 personFuncName5 personFuncName6的调用都是直接访问函数地址的,说明在编译过程中就已经确定了函数地址,也就是静态派发调度方式了。

sil_vtable

sil_vtable.png

再看虚拟函数表中只有personFuncName1 personFuncName2 personFuncName5 personFuncName8 虽然personFuncName5在这表里面但是明细和其它不一样。这是因为它是private修饰的方法为静态派发调度方式。

@objc修饰的方法

@objc修饰的方法也是函数派发调度方式。在方法实现上看sil代码发现有两个实现,ClassPerson.personFuncName8() @objc ClassPerson.personFuncName8()并且第二个方法以静态派发方式调用了第一个方法。第二个方法就是暴露给oc调用的接口方法。

@objc.png

dynamic修饰的方法

我们用dynamic修饰了teach()方法,编译成sil代码后方法实现前有个[dynamically_replacable]字面意思就是动态可被替换的dynamic修饰的方法就是动态的可被替换,可被替换是指在OC运行时的方法交换的场景下可被替换。

dynamically_replaceable.png

@_dynamicReplacement(for: teach())

代码语言:javascript复制
/// swift5 方法替换 需要对被替换的方法加dynamic修饰
    @_dynamicReplacement(for: teach())
    private func teach1() {
        debugPrint(#function)
    }

在swift5中进行dynamic修饰的方法替换。在编译的sil代码中可以查看到teach1()方法的实现就是替换了teach()方法。可以在sil_vtable中找到@$s6Person05ClassA0C5teachyyF这个指向的就是teach()方法。

dynamic_replacement_for.png

@objct dynamic修饰的方法

在上面init初始化方法调用中可以看到,调度方式是objc_method这是oc特有的方式-消息转发objc_msgSend

运行程序进入到汇编代码中就可以看到该方法是采用objc_msgSend方式调度

objc_msgSend.png

验证Struct中的方法调度

1、创建StructPerson.swift源文件

代码语言:javascript复制
struct StructPerson {
    var name: String
    
    @discardableResult init(name: String) {
        self.name = name
        
        structFuncName1()
        
        structFuncName2()
        
        structFuncName3()
        
        structFuncName4()
    }
    
    func structFuncName1() {
        
    }
    
    mutating func structFuncName3() {
        name  = #function
    }
    
    private func structFuncName4() {
        
    }
    
}

extension StructPerson {
    func structFuncName2() {
        
    }
}

2、编译成sil文件 找到init(name:)方法,查看里面的方法调用方式。可以看到不管是私有方法还是扩展里面的方法都是静态派发的方式function_ref

struct.png

验证Protocol中的方法调度

1、创建ProtocolPerson.swift源文件

代码语言:javascript复制
protocol ProtocolPerson: NSObjectProtocol {
    func protocolFuncName1()
    func protocolFuncName2()
}

extension ProtocolPerson {
    
    func protocolFuncName3() {
        
    }
}


class ClassPersonBtn {
    weak var delegate: ProtocolPerson?
    
    func click() {
        delegate?.protocolFuncName1()
        delegate?.protocolFuncName2()
    }
  
}

class ClassPersonOwner: NSObject, ProtocolPerson {
    let btn = ClassPersonBtn()
    
    override init() {
        super.init()
        btn.delegate = self
        protocolFuncName3()
    }
    
    func protocolFuncName1() {
        
    }
    
    func protocolFuncName2() {
        
    }
}

2、编译成sil文件 protocolFuncName1 protocolFuncName2 这两个方法都是定义在协议内的,采用的都是函数派发调度方式。

protocol_class_method.png

protocolFuncName3这个方法是定义在协议扩展内的,采用的是静态派发方式。可以理解只要是方法是在extension中实现的都是采用静态派发方式调度。

0 人点赞