前言

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

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

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


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

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

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

extension PersonClass {
func changClassName10() {}
}

SIL代码:

断点,汇编跟踪一下:

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

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

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

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

【注意】类的延展方法时,需要注意:

  • 不可以在子类里重写父类延展里面的方法,子类可以重写父类本类定义的方法
  • 不可以在延展里 存在/重写 已在继承连中存在的同名方法

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

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

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

SIL 代码:

sil_vtable SIL :

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

再看方法调度 SIL :

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

再汇编调试一下:

编译时已经确定了函数的地址,运行时,直接执行。所以private/fileprivate 访问修饰符修饰的是静态派发。

前面我们提到“函数表存放类中可能是动态派发去执行的函数”, 注意是可能哦, 不是一定的

小结:

private/fileprivate 访问修饰符修饰的是静态派发。

public/open/internal 访问修饰符修饰的是函数派发。

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

源码:

@objc func changClassName7() {}

vtable SIL:

方法调度 SIL:

运行、汇编:

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

那 @objc 的作用是什么呢?

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

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

所以这个方法是暴露给OC中调用的接口方法. 没有@objc 修饰的方法,OC 中是无法使用的。具体的混编步骤,以后会在 [Swift 与 OC 混编] 这篇文章中讲到

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

源码如下:

dynamic func changClassName8() {}

vtable SIL:

方法调度 SIL:

运行、汇编:

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

dynamic 有什么作用呢?

看看方法定义SIL:

与普通函数不同的是,在方法定义时,多了一个dynamically_replacable的标签,表明这是一个动态方法,可以被替换。可被替换是指在OC运行时的方法交换的场景下可被替换。

如果想要对Swift 方法进行方法交换,需要对被替换的方法加dynamic修饰。 

再使用@_dynamicReplacement(for: teach)来完成替换.

示例代码如下:

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”

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

源码如下:

@objc dynamic func changClassName9() {}

vtable SIL:

函数表中没有changClassName9的函数。

方法调度 SIL:

与普通的函数派发方法调用时不同,不是以 class_method 方式,是以objc_method方式

运行、汇编调试:

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

5. static 修饰

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

源码如下:

static func changClassName11() {}

vtable SIL:

方法调度 SIL:

运行、汇编调试:

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

6. final 修饰

final修饰符的几点使用原则

  • final修饰符只能修饰,表明该类不能被其他类继承,也就是它没资格当父类。
  • final修饰符也可以修饰类中的方法, 表明该方法不能被子类重写
  • final不能修饰结构体、枚举、协议。

源码如下:

final func changClassName1() {}
table SIL:
 

方法调度 SIL:

行、汇编调试:

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

5. 总结

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

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

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

  • Direct(静态调度):在 SIL 文件中,以function_ref 的方式获取函数
    •   结构体的普通方法
    • 类中方法的修饰符为 :final / private/fileprivate / static
    • 类、结构体、协议延展内的方法
    • Table(函数表调度) :在 SIL 文件中,以 class_method 的方式,通过 Vtable 获取函数
    •   普通类中的方法
    • 类中方法的修饰符为:open/public/internal / @objc / dynamaic
    • Message(消息转发调度):在 SIL 文件中,以 objc_method 的方式获取函数
    •   @objc dynamaic
    • witness_method(协议表调度):在 SIL 文件中,以 witness_method 的方式, 通过 PWT  获取函数
    • 遵守了协议并实现了协议本身定义的方法的结构体或者类

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

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



最新文章

  1. 从零开始编写自己的C#框架(15)——Web层后端登陆功能
  2. Visual Studio 2013支持Xamarin的解决方案
  3. (转)初探Backbone
  4. mvc 简单笔记
  5. 六 mybatis高级映射(一对一,一对多,多对多)
  6. Jumping Cows_贪心
  7. POJ 3978 Primes(素数筛选法)
  8. [Objective-c 基础 - 2.4] 多态
  9. leetcode面试准备:Add and Search Word - Data structure design
  10. Redis状态和信息查看
  11. handler的使用
  12. BootStrap Table使用小结
  13. Netty学习笔记(一) 实现DISCARD服务
  14. Python学习基本小练习
  15. Loj 103、10043 (KMP统计子串个数)
  16. js 对url进行编码和解码的三种方式
  17. cordic——sincos
  18. 远程客户端连接MysqL数据库太慢解决方案
  19. Spring学习总结之高级装配
  20. 021.5 IO流——字符流

热门文章

  1. Data Augmentation
  2. VSCode添加某个插件后,Python 运行时出现Segmentation fault (core dumped) 解决办法
  3. JDBC分页查询及实现
  4. golang net/http包
  5. Spring Boot 入门系列(二十四)多环境配置,3分钟搞定!
  6. IDEA中mybatis generator使用
  7. Docker基本指令
  8. WebService学习总结(四)--基于CXF的服务端开发
  9. mybatis动态sql以及分页
  10. Django项目中的模板继承