方法调度
结论
-
Class中的方法-
publicopeninternal方法调度都是函数派发方式 -
privatefileprivatefinal方法调度为静态派发方式 -
extension中的方法都为静态派发方式
-
-
Struct中的方法- 全部都是静态派发调度方式:
mutatingextensionpublicprivate...
- 全部都是静态派发调度方式:
-
Protocol中的方法- 方法最初定义在协议本身内, 则方法以协议函数表的方式调度
- 方法最初定义在协议延展内, 则方法以静态派发的方式调度
验证Class中的方法调度
1、创建ClassPerson.swift原始文件。
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文件。
// 编译 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能否确定这就是personFuncName4()方法呢,使用下面命令行:
xcrun swift-demangle <混写后的名称>

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

-
personFuncName3personFuncName4是扩展方法 -
personFuncName5是private修饰的方法 -
personFuncName6是final修饰的方法
以上三种情况定义的方法都是静态派发调度方式。
断点汇编查看
xcode顶部导航栏选择Debug->Debug Workflow->Always Show Disassemebly,在init()方法最后打个断点,运行程序:

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

再看虚拟函数表中只有personFuncName1 personFuncName2 personFuncName5 personFuncName8 虽然personFuncName5在这表里面但是明细和其它不一样。这是因为它是private修饰的方法为静态派发调度方式。
@objc修饰的方法
@objc修饰的方法也是函数派发调度方式。在方法实现上看sil代码发现有两个实现,ClassPerson.personFuncName8() @objc ClassPerson.personFuncName8()并且第二个方法以静态派发方式调用了第一个方法。第二个方法就是暴露给oc调用的接口方法。

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

@_dynamicReplacement(for: teach())
/// swift5 方法替换 需要对被替换的方法加dynamic修饰
@_dynamicReplacement(for: teach())
private func teach1() {
debugPrint(#function)
}
在swift5中进行dynamic修饰的方法替换。在编译的sil代码中可以查看到teach1()方法的实现就是替换了teach()方法。可以在sil_vtable中找到@$s6Person05ClassA0C5teachyyF这个指向的就是teach()方法。

@objct dynamic修饰的方法
在上面init初始化方法调用中可以看到,调度方式是objc_method这是oc特有的方式-消息转发objc_msgSend。
运行程序进入到汇编代码中就可以看到该方法是采用objc_msgSend方式调度

验证Struct中的方法调度
1、创建StructPerson.swift源文件
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

验证Protocol中的方法调度
1、创建ProtocolPerson.swift源文件
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 这两个方法都是定义在协议内的,采用的都是函数派发调度方式。

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

