方法调度
结论
-
Class
中的方法-
public
open
internal
方法调度都是函数派发方式 -
private
fileprivate
final
方法调度为静态派发方式 -
extension
中的方法都为静态派发方式
-
-
Struct
中的方法- 全部都是静态派发调度方式:
mutating
extension
public
private
...
- 全部都是静态派发调度方式:
-
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
的就是静态派发调度方式。
-
personFuncName3
personFuncName4
是扩展方法 -
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
中实现的都是采用静态派发方式调度。