swift派发

作者: 狗蛋的春天 | 来源:发表于2021-04-27 16:52 被阅读0次

方法派发是一个术语,是指程序确定应该执行哪个操作的机制(通过操作,这里指的是一组指令)。有时,我们仅希望在运行时确定具体的方法行为。这种机制导致了方法派发机制的不同,每种机制各有其优缺点。

静态派发

有时也被称为直接调用/派发。
如果一个方法是静态派发的,编译器就可以在编译时找到指令所在的位置。这样,当调用这种函数时,系统将直接跳转到此函数        的内存地址以执行操作。这种直接行为导致执行速度非常快,并且还允许编译器执行各种优化,例如内联。实际上,由于性能的巨  大提高,编译管道中存在一个阶段,在此阶段,编译器将在适用的情况下尝试使函数静态化。这种优化称为去虚拟化[1]。

动态派发

使用这种方法,程序直到运行时才知道要选择哪种实现。
尽管静态派发是极为轻量的,但它限制了灵活性,特别是在多态方面。这也是为什么动态派发被 OOP 语言广泛支持的原因
每种语言都有其自己的机制来支持动态调度。Swift提供了两种实现动态性的方法:table 派发(表派发)和message 派发(消息    派发)。

Table 派发 (表派发)

这是编译语言中最常见的选择。通过这种方法,一个类与一个所谓的 virtual table 相关联,虚拟表包含了指向对应于该类的实际    实现的函数指针数组。
请注意,vtable 是在编译时构造的。因此,与静态派发相比,真多了两个附加指令(read和jump)。从理论上讲,表派发应该也    很快。

Message 派发(消息派发)

实际上,正是由 Objective-C 提供的这种机制(有时这被称作消息传递[2]),Swift 代码仅使用了 Objective-C 运行库。每次调用     Objective-C 方法时,调用都会传递给 objc_msgSend ,由后者负责处理查找工作。从技术上讲,运行时从给定类开始,抓取类的层    次结构以便确认调用哪个方法。
与表派发不同的是,message passing dictionary 在运行时可以发生修改,从而使我们能够在运行时调整程序的行为。利用这一特    点,Method swizzling 成为最流行的技术。
消息派发是三种派发(实际上是 4 种)中最具动态性的。作为交换,尽管系统通过实现缓存机制来保障查找的性能,但是其解决  实现的成本可能会稍微高一些。
这种机制是 Cocoa 框架的基石。查看代码 Swift 的源码,你会发现 KVO 就是利用 swizzling 实现的。

关于 Swift 派发的两个问题

对于一个给定函数,它使用了什么样的派发方式?证据是什么?

确定派发机制的方法

作为怀疑者,我对问题的第二部分更感兴趣。提出一个假设很容易,但是要一直进行检验并不是一件容易的事。经过数小时的搜寻,我碰巧知道了SIL文档[3],该文档合理地解释了派发策略的存在。这是一个简短的摘要:
如果函数使用表派发,那么它会出现在 vtable(或用于协议的 witness_table)中
sil_vtable Animal {
     #Animal.makeSound!1: (Animal) -> () -> () : main.Animal.makeSound() -> () // Animal.makeSound()
    ......
}
如果函数是通过消息派发的,那么调用中应该出现关键字 volatile 。此外,你将找到两个标记 foreign 和 objc_method, 表明该函数/方法是使用 Objective-C 运行时调用的。
  %14 = class_method [volatile] %13 : $Dog, #Dog.goWild!1.foreign : (Dog) -> () -> (), $@convention(objc_method) (Dog) -> () 

如果并没有出现上面两种情况,则说明该函数/方法是使用静态调度的。

注意

首先,Struct 或 任何值类型的函数必须静态派发。这是有道理的,因为它们永远不会被覆盖。
明确执行
具有 final 关键字的函数也会被静态派发。
具有 dynamic 关键字的函数将通过消息传递派发 —— 从 Swift 4.0 开始
带有 dynamic 关键字的函数对 Objective-C 是隐式可见的。同时,Swift 4 要求你使用 @objc 属性显式声明它。
普通扩展(即没有 final、dynamic、@objc)是直接派发的。现在,回想一下你可能曾经遇到过的编译错误:declarations in     extensions cannot override yet. 这是因为这些功能当然是遵循静态派发的。
你可能会问:“如果我想让这些扩展成为动态的呢?”。你明白了!如果扩展名是动态的,那就可以覆盖它。
 extension Animal {
     func eat() { }
     @objc dynamic func getWild() { }
 }
class Dog: Animal {
   override func eat() { } // Compiled error!
   @objc dynamic override func getWild() { } // Ok :)
}
其他情况
protocol Noisy {
    func makeNoise() -> Int // TABLE
}
extension Noisy {
   func makeNoise() -> Int { return 0 } // TABLE
   func isAnnoying() -> Bool { return true } // STATIC
}
class Animal: Noisy {
   func makeNoise() -> Int { return 1 } // TABLE
   func isAnnoying() -> Bool { return false } // TABLE
 @objc func sleep() { } // Still TABLE
}
extension Animal {
     func eat() { } // STATIC
    @objc func getWild() { } // MESSAGE
}
Noisy.isAnnoying() 和 Animal.getWild() 是静态派发的,因为它们是扩展。
Noisy.makeNoise() 尽管具有默认实现,但仍使用表派发。
我们必须谨慎使用 isAnnoying()。请考虑以下两种用法。
animal2.isAnnoying() 选择协议扩展的实现(因为它是直接方法,不需要查找)。以这种方式使用可能是一个错误的来源

let animal1 = Animal()
print(animal1.isAnnoying()) // Value: false
let animal2: Noisy = Animal()
print(animal2.isAnnoying()) // Value: true
反过来说,animal1.makeNoise() 和 animal2.makeNoise() 产生相同的结果,因为协议是通过查找表来解决的。
被 @objc 关键字修饰的 @objc func sleep() 意味着该函数对 Objective-C 可见。但这并不一定意味着派发时一定选择 Objective-C     方法来执行。从函数调用的 SIL(见下文),我们可以看到 $@convention(method) ,这意味着选择 Swift 方法而不是 objc 方法。

原则是什么?

优先考虑直接调用(静态派发)。
如果需要覆盖,则表派发是下一个候选。
需要对 Objective-C 进行覆盖和可见性吗?然后发送消息。
另一个关键是明确性更好。。隐式推断(如带有 @objc 的扩展)可能会发生变化。
以下是一些常见案例的总结。建议你通过读取生成的 SIL 来进行双重检查。

直接调用    Table   Message
明确执行    final, static   —   dynamic
值类型 所有方法    —   —
协议  拓展中的方法  定义的方法   —
类   拓展中的方法  定义的方法   带有 @objc 的扩展

结论

在这篇文章中,我们了解了什么是方法派发,以及 Swift 中的不同派发类型。我们通过一些例子来了解 Swift 是如何解析特定函数    的。另外,通过阅读 SIL,我们收集了关于函数具体遵循哪种派发假设的证据。
静态派发由于其出色的性能而变得越来越重要。这就是为什么 Swift 是 Swift(雨燕)(相对于 Objective-C —— 一种动态语言)。
尽管消息派发的性能似乎比较差,但它提供了极大的灵活性,以至于支持一系列很酷的技术。
理解方法派发至关重要。它不仅可以帮助编写更优的代码,还可以避免一些奇怪的错误。
在上面提到的这些之中,我们抛弃了编译器的优化。编译器优化的代码的能力很大程度上取决于我们编写代码的方式 :)。

转载的

相关文章

  • Swift派发机制

    Swift之所以速度比Object-c快,我觉得跟他的派发机制有关。下面我们聊聊Swift的派发机制。 派发机制分...

  • 浅谈Swift派发机制

    函数的派发机制分为:静态派发(直接派发)、函数表派发、消息派发 1、Swift中所有ValueType(值类型:S...

  • iOS知识复习笔记(16)---swift相关

    一、函数的派发方式 swift函数的派发机制有三类:static直接派发(静态)派发、table函数派发,mess...

  • Swift派发机制

    Swift派发分:静态派发和动态派发 静态派发:(又叫:直接调用) 静态派发机制,同时支持值类型和引用类型;静态派...

  • Swift的函数派发

    前言 对于Swift的学者来说函数派发有很大的误区:就是认为Swift沿用Objective-C的消息派发机制,且...

  • swift派发

    方法派发是一个术语,是指程序确定应该执行哪个操作的机制(通过操作,这里指的是一组指令)。有时,我们仅希望在运行时确...

  • Swift - 三种函数派发

    Swift 的函数派发可以分为 静态 和 动态 两种机制,而动态派发又分为 函数表派发 和 消息派发 。 Swif...

  • Swift 派发机制

    此篇博客用来自我学习,来源戴铭大佬的这篇博客 Swift 派发机制 派发目的是让 CPU 知道被调用的函数在哪里。...

  • Swift派发方式

    简介 作为iOS开发,大家都知道OC的派发方式其实就是利用runtime采用了运行时机制使用obj_msgSend...

  • Swift 派发机制

    表格总结如下: 值类型使用直接派发 协议、类的初始化声明使用函数表派发 协议、类的 extension 使用直接派...

网友评论

    本文标题:swift派发

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