Swift 函数派发的一点了解

题外话

那天和群里一我的聊天,他去过头条京东面试,用的 Swift,问的问题我都没都听过,其中就有这个 Swift 派发机制。决定仍是看下这些基础原理,而后忽然想到一个问题,为何要写博客呢,如今的想法是和别人交流的时候能逻辑很清晰的解释什么是 派发机制,否则以个人惯性,可能时间过一点就说的迷迷糊糊了。看了几篇文章,这两篇说得不错。git

kemchenj.github.io/2016-12-25-…github

zhaoxinyu.me/2018-04-08-…面试

www.jianshu.com/p/cfe7da018…swift

下面文章写的有点怪,是先带着结果看问题的。由于这也是我最开始的思路,和其余博客不太同样,我并非先遇到问题。这个话题是我想了解因此就直接查的问题,得出结论,而后根据结论分析一些代码例子,最后一步是提供验证这些结论是否正确的方法,由于最后一步比较乏味。ide

派发机制

经过阅读上面两篇文章和 SIL 代码验证事后,swift 有四种派发方式:函数

  • static dispatch:静态派发(直接派发)
  • table dispatch:函数表派发, 经过 SIL 分析,swift 中有两种函数表,协议用的 witness_table,类用的 virtual_table
  • message dispatch:消息派发,OC 中经常使用的派发方式

第一篇文章中关于这个部分说的比较详细,大段抄过来不太好。。优化

总结

派发机制总结表(我没验证全,用 SIL 验证了部分,剩余是看上面的文章的)ui

  1. 表里左边的便是声明引用类型,非实际对象类型;
  2. 根据 SIL 在非编译器优化的状况下得出的结论,有的状况下会把 table 派发优化成直接派发,不在讨论状况中

dispatch.png

图中那个问好是我实践的和其余文章说的不同,最下面有说spa

除上图外的重要点:翻译

  • 引用的类型决定了派发的方式。
  • NSObjectClass 和 Class 没什么区别(至少编译器未优化前是这样)

根据结论用代码看问题

根据以上逻辑,有两个代码问题,刚看的时候有点疑惑,如今能够用上面的东西解释下

  1. 先看一个简单的逻辑
protocol MyProtocol {}

extension MyProtocol {
    func testFuncA() {
        print("protocol - funcA")
    }
}

class Myclass: MyProtocol {
    func testFuncA() {
        print("class - funcA")
    }
}

let x: MyProtocol = Myclass()
x.testFuncA() // protocol - funcA
复制代码

因为 x 是协议类型,因此先会去查协议的函数表,而协议里并无 funcA 的声明,因此协议函数表里就不存在这个方法,也不会去根据表查找实现,就走了协议的 extension 的直接派发。


  1. 再看另外一个状况
protocol MyProtocol {
	func testFuncA()
}
... (和上面同样)

let x: MyProtocol = Myclass()
x.testFuncA() // class - funcA
复制代码

因为协议函数表里声明了 funcA,因此用协议函数表查找实现,找到了在 Myclass 中的实现,走的函数表派发。


第三个问题 是上述文章提到的 SR-103

protocol Greetable {
    func sayHi()
}

extension Greetable {
    func sayHi() {
        print("Hello")
    }
}

class Person: Greetable {}

class LoudPerson: Person {
    func sayHi() {
        print("HELLO")
    }
}

let x: Greetable = LoudPerson()
x.sayHi() // Hello,protocol

复制代码

这个被肯定是一个bug。按照正常的理解,协议函数表会查找子类的 sayhi 实现,可是实际上只找了遵循协议的那个 person 类,找不到,因此就走了协议 extension 的直接派发。可是三年过去了,这个bug好像并没被修复。。

SIL 验证过程

Swift Intermediate Language(SIL) 是 Swift 在编译过程当中的中间产物。如今把这个文件解析成 SIL,上面第二个文章也说了方法,这里补充说明下吧。

swiftc -emit-silgen xxx.swift -Onone > xxxx.sil

struct TestStruct {
    func testFunc1() { print("ss") }
}

let t = TestStruct()
t.testFunc1()

复制代码

以下图所示,咱们要分析的是 main 中的代码,经过搜寻函数名关键字能够看到 %9 那里是 function_ref,能够看出这个是直接派发

如何看是不是派发方式:

  • function_ref:直接派发
  • class_method:虚函数表派发
  • witness_method:证据表派发(不知道咋翻译了)
  • objc_method:消息机制派发

sil-struct.png

问题:

  1. 两个文章都说 subclass.extension 会采用 消息机制派发,可是我实验不出来这种状况,以前他们有个例子是用在 extension 中 override ,可是如今这种写法编译器报错了,不知道是否是由于这个
  2. 为何函数表的方式没法动态生成函数插入到表里,消息派发机制能够?消息派发机制如何动态添加到函数表的,暂时猜想 runtime 的函数表是多个表的集合,涉及到了 runtime 原理,暂时不太懂。
相关文章
相关标签/搜索