Swift学习

Swift 函数派发机制

2020-01-11  本文已影响0人  我是熊大啊

编程语言 函数派发机制有三种: 【原文链接】

函数派发是程序判断使用哪种途径去掉用一个函数的机制,每次函数调用时都会被除法。
了解函数的派发机制,对于写出高性能代码来说十分有必要。
Java:默认函数表派发,可以通过final修饰修改成直接派发。
C++:默认是直接派发,但可以通过virtual修饰符来改成函数表派发
Objective-C:总是使用消息派发的机制,但允许开发者使用C直接派发来获取性能的提高。

注意:

Swift 函数派发机制,以上 三种都会涉及到。

派发方式:(Types of Dispatch)

程序派发的目的是为了告诉CPU需要被调用的函数在哪里。在我们深入理解Swift派发机制之前,先来了解下这三种派发机制,以及没中方式在动态性和性能之间的取舍。

直接派发:

直接派发是最快的,不止是因为需要调用的指令集少,并且编译器还有很大的优化空间(比如:函数内联)。直接派发也称为静态调用。
然而,对于编程来说直接调用也是最大的局限,而且因为缺乏动态性所以没办法支持继承。

函数表派发

函数表派发是编译型语言实现动态行为最常见的实现方式。函数表使用了一个数组来储存类声明的每一个函数的指针。大部分语言把这个称为『virtual table』虚函数表,Swift里称为 『witness table』。每一个类都会维护一个虚函数表,里边记录着类的所有函数,如果父类被override的话,表里只会保存override之后的函数,子类新增后会被插到这个数组的最后,运行时会决定这一个表实际要被调用的函数。
当一个函数调用时,首先要读取函数表,在读取函数对应的索引,然后跳转。

消息派发机制

消息机制是调用函数的最动态的方式,也是Cocoa的基础,这样的机制催生了KVO、UIAppearence、CoreData等功能,这种运作方式的关键在于开发者可以在运行时改变函数的行为,不知可以通过swizzling 来改变,还可以用 isa-swizzling修改对象的继承关系,可以在面向对象的基础上实现自定义派发。

Swift 是怎么派发的呢?

注意:通过关键字可以指定派发方式

final ----类里面的函数使用直接派发
dynamic ----函数使用消息机制派发
@inline ----直接派发
@objc ----消息机制派发

@objc 和 @nonobjc 显式地声明了一个函数是否能被 Objective-C 的运行时捕获到. 使用 @objc 的典型例子就是给 selector 一个命名空间 @objc(abc_methodName), 让这个函数可以被 Objective-C 的运行时调用. @nonobjc 会改变派发的方式, 可以用来禁止消息机制派发这个函数, 不让这个函数注册到 Objective-C 的运行时里.

image

可见的都会被优化 (Visibility Will Optimize)

Swift 会尽最大能力去优化函数派发的方式. 例如, 如果你有一个函数从来没有 override, Swift 就会检车并且在可能的情况下使用直接派发. 这个优化大多数情况下都表现得很好, 但对于使用了 target / action 模式的 Cocoa 开发者就不那么友好了. 例如:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.rightBarButtonItem = UIBarButtonItem(
        title: "登录", style: .plain, target: nil,
        action: #selector(ViewController.signInAction)
    )
}
private func signInAction() {}

这里编译器会抛出一个错误: Argument of '#selector' refers to a method that is not exposed to Objective-C (Objective-C 无法获取 #selector 指定的函数). 你如果记得 Swift 会把这个函数优化为直接派发的话, 就能理解这件事情了. 这里修复的方式很简单: 加上 @objc 或者 dynamic 就可以保证 Objective-C 的运行时可以获取到函数了. 这种类型的错误也会发生在UIAppearance 上, 依赖于 proxy 和 NSInvocation 的代码.

另一个需要注意的是, 如果你没有使用 dynamic 修饰的话, 这个优化会默认让 KVO 失效. 如果一个属性绑定了 KVO 的话, 而这个属性的 getter 和 setter 会被优化为直接派发, 代码依旧可以通过编译, 不过动态生成的 KVO 函数就不会被触发.

Swift 的博客有一篇很赞的文章描述了相关的细节, 和这些优化背后的考虑.

image

了解更多:https://kemchenj.github.io/2016-12-25-1/

上一篇下一篇

猜你喜欢

热点阅读