前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >swift方法调度总结

swift方法调度总结

作者头像
用户6094182
发布2022-09-28 13:50:04
4700
发布2022-09-28 13:50:04
举报
文章被收录于专栏:joealzhoujoealzhou

方法调度

结论
  • 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中实现的都是采用静态派发方式调度。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 方法调度
    • 结论
      • 验证Class中的方法调度
        • 验证Struct中的方法调度
          • 验证Protocol中的方法调度
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档