美文网首页
iOS Swift结构体与类的方法调度

iOS Swift结构体与类的方法调度

作者: 一眼万年的星空 | 来源:发表于2021-10-15 16:32 被阅读0次

    前言

    hello,小伙伴们:在忙碌中闲暇之余给大家聊聊swift的知识点,今天给大家带来的是swift中结构体与类的方法调度详细区别,希望对你有所帮助,好了废话不用多说,接下来步入主题!

    1.普通方法时两者方法调度的区别

    ● 结构体中的普通方法调度是静态派发的方式
          ○ 详细分析会在以后: 方法调度之普通结构体方法 阐述
    ● 类中的普通方法是以函数派发的方式去调度的。
          ○  详细分析会在以后:方法调度之普通方法 阐述
    

    2.协议中两者方法调度的区别

    ● 以类/结构体直接声明的,
    ○ 结构体:方法调度都是静态调度
    ○ 类:方法调度都是函数调度
    ● 以协议类型声明的, 无论协议的实现是类还是结构体:
    ○ 方法最初定义在协议本身内, 则方法以协议函数表的方式调度
    ○ 方法最初定义在协议延展内, 则方法以静态派发的方式调度

    3.extension对类中方法调度的影响

    swift:
    extension PersonClass {
        func changClassName10() {}
    }
    

    SIL代码:


    SIL SIL

    断点,汇编跟踪一下:


    SIL

    可以看到 changClassName10 这个方法在执行的时候,由function_ref修饰,sil_vtable 中的函数列表里面没有。在编译时已经确定了函数的地址,运行时,直接执行。所以延展内的方法是静态派发。

    🤔 思考:为什么普通函数放到了延展中,它就不在函数表中,不是函数派发的方式调度了呢?

    我们在方法调度之普通方法一文中讲解过:函数表是数组结构,里面的函数是按顺序排列的

    如果父类存在延展方法,且放在函数表里,就需要考虑它和子类方法的排列顺序问题。哪个在前,取决于文件的编译顺序。如果子类先编译,父类后编译,还要将子类的所有方法都顺次移位,再将延展方法插入到父类方法之后。这样做,编译效率就会降低将延展方法使用静态派发,是一种以空间换时间的方法。协议的延展中的方法,也是静态派发的,他们是一样的道理。

    【注意】类的延展方法时,需要注意:
    ● 不可以在子类里重写父类延展里面的方法,子类可以重写父类本类定义的方法
    ● 不可以在延展里 存在/重写 已在继承连中存在的同名方法。

    swift

    4.修饰词对类方法调度的影响

    1. 访问修饰符修饰的方法

    swift
        private func changClassName2() {}
        fileprivate func changClassName3() {}
        public func changClassName4() {}
        internal func changClassName5() {}
        open func changClassName6() {}
    

    SIL 代码:
    sil_vtable SIL :

    sil_vtable SIL

    虽然所有函数修饰符修饰的方法,都在函数表中存在,但是明显 private 修饰的 changClassName2 , 与 fileprivate 修饰的changClassName3 与众不同,他们在方法名的后面有** in _12232F587A4C5CD8B1EEDF696793A4FC **。 这个不同,会导致它们在方法调度的时候,和其他的访问修饰符什么区别呢?
    再看方法调度 SIL :'

    SIL

    可以发现 private 修饰的 changClassName2 , 与 fileprivate 修饰的changClassName3 在调用时,前面的修饰符是由function_ref 修饰,而不是class_method修饰。所以是静态派发?
    再汇编调试一下:

    SIL

    编译时已经确定了函数的地址,运行时,直接执行。所以private/fileprivate 访问修饰符修饰的是静态派发。
    前面我们提到“函数表存放类中可能是动态派发去执行的函数”, 注意是可能哦, 不是一定的。

    小结:
    private/fileprivate 访问修饰符修饰的是静态派发。
    public/open/internal 访问修饰符修饰的是函数派发。

    2. @objc 修饰的方法: 函数表

    源码:

    swift
    @objc func changClassName7() {}
    

    vtable SIL:

    vtable SIL

    方法调度 SIL:

    vtable SIL

    运行、汇编:

    汇编

    所以: 在swift 中调用 @objc 修饰的方法是函数派发,没什么特别的。
    那 @objc 的作用是什么呢?

    我们来看一下changClassName7 方法定义在 SIL 代码:

    汇编

    可以看到,除了正常的定义changClassName7 方法以外,额外底层多生成了一个 @objc main.PersonClass.changClassName7()这个方法内部又调用了 正常定义的changClassName7。

    所以这个方法是**暴露给OC中调用的接口方法. 没有@objc 修饰的方法,OC 中是无法使用的。具体的混编步骤,可以参考这篇文章☞ **Swift 与 OC 混编

    3. dynamic 修饰的方法:函数表

    源码如下:

    swift
    dynamic func changClassName8() {}
    

    vtable SIL:


    vtable SIL

    方法调度 SIL:

    方法调度 SIL

    运行、汇编:


    汇编

    在编译时,不能确定方法的地址,在函数表内,所以dynamic的方法调度方式是函数派发。

    dynamic 有什么作用呢?
    看看方法定义SIL:


    dynamic

    与普通函数不同的是,在方法定义时,多了一个dynamically_replacable的标签,表明这是一个动态方法,可以被替换。可被替换是指在OC运行时的方法交换的场景下可被替换。
    **如果想要对Swift 方法进行方法交换,需要对被替换的方法加dynamic修饰。 **
    再使用@_dynamicReplacement(for: teach)来完成替换.
    示例代码如下:

    swift
    class PersonClass: NSObject {
        dynamic func teach() {
            print("teach")
        }
    }
    
    extension PersonClass {
        // swift 5 中提供的方法交换方式
        // 将 teach 方法替换成这行代码下面的teach1方法
        // 执行 teach 方法,实际上执行的是 teach1方法
        @_dynamicReplacement(for: teach)
        func teach1() {
            print("teach1")
        }
    }
    let t = PersonClass()
    t.teach()
    

    所以打印结果是:“teach1”


    teach1

    4. @objc dynamic 修饰的方法:消息转发

    源码如下:

    swift
    @objc dynamic func changClassName9() {}
    

    vtable SIL:

    vtable SIL
    函数表中没有changClassName9的函数。

    方法调度 SIL:

    方法调度 SIL

    与普通的函数派发方法调用时不同,不是以 class_method 方式,是以objc_method方式
    运行、汇编调试:

    汇编调试

    汇编调试时,看到了熟悉的objc_msgSend。这是OC的消息转发的方式进行方法调度。

    5. static 修饰

    static修饰的方法,叫做类方法,可以直接由类名去调用,无需创建实例对象。
    源码如下:

    swift
    static func changClassName11() {}
    

    vtable SIL:


    vtable SIL

    方法调度 SIL:


    方法调度 SIL

    运行、汇编调试:


    汇编调试

    以function_ref 的方式获取函数, 所以是静态派发。

    6. final 修饰

    final修饰符的几点使用原则
    ● final修饰符只能修饰类,表明该类不能被其他类继承,也就是它没资格当父类。
    ● final修饰符也可以修饰类中的方法, 表明该方法不能被子类重写。
    ● final不能修饰结构体、枚举、协议。
    源码如下:

    swift
    final func changClassName1() {}
    

    vtable SIL:


    vtable SIL:

    方法调度 SIL:

    方法调度 SIL

    运行、汇编调试:


    汇编调试

    以function_ref 的方式获取函数, 所以是静态派发。

    5. 总结

    函数表内的函数,不一定是函数派发的方式去调度。但是不在函数表中的,一定不是函数派发的方式。

    在调用时获取函数的方式可以作为判断调度方法的依据。下面是对应不同的获取函数的方式的不同调度方式:

    Swift 中的方法调度分2大类:动态****调度****与静态****调度

    • Direct(静态调度):在 SIL 文件中,以function_ref 的方式获取函数

    • **🌰 **结构体的普通方法

    • 类中方法的修饰符为** :**final / private/fileprivate / static

    • 类、结构体、协议延展内的方法

    • **Dynamic Dispatch(****动态调度****)****:****官方文档传送门☞ **Dynamic Dispatch

    • Table(函数表调度) :在 SIL 文件中,以 class_method 的方式,通过 Vtable 获取函数

    • **🌰 **普通类中的方法

    • 类中方法的修饰符为:open/public/internal / @objc / dynamaic

    • Message(消息转发调度):在 SIL 文件中,以 objc_method 的方式获取函数

    • **🌰 **@objc dynamaic

    • witness_method(协议表调度):在 SIL 文件中,以 witness_method 的方式, 通过 PWT 获取函数

    • 🌰 遵守了协议并实现了协议本身定义的方法的结构体或者类

    好了,小编给大家整理的swift的结构体与类的方法调度,若有收获,就点个赞吧!

    青山不改,绿水长流,后会有期,感谢每一位佳人的支持!

    相关文章

      网友评论

          本文标题:iOS Swift结构体与类的方法调度

          本文链接:https://www.haomeiwen.com/subject/loymoltx.html