dispatch部分是根据其余人的文章整理的。git
思考题部分是在项目中遇到,但愿你们在看完派发后能有所思考。github
咱们都知道一个方法会在运行时被调用,一个方法被唤起,是由于编译器有一个计算机制,用来选择正确的方法,而后经过传递参数来唤起它.编程
这个机制一般被成为派发(dispatch). 分派就是处理方法调用的过程.swift
分派在处理方法调用的时候,可能会存在多个合理的可被调用的方法列表,此时就须要去选择最正确的方法.选择正确方法的整个过程,就是派发(dispatch). 每种编程语言都须要分派机制来选择正确的唤起方法.后端
方法从书写完成到调用完成,归纳上会经历编译期和运行期两个阶段,而前面说的x选择哪一个方法被执行,也是在这两个时期进行的.数组
选择正确方法的阶段,能够分为编译期和运行期,而分派机制经过这两个不一样的时期分为两种: 静态分派(static dispatch)和 动态派发(dynamic dispatch).安全
static dispatch能够确保某个方法只有一种实现.static dispatch明显的快于dynamic dispatch,由于dynamic dispatch自己就意味着较高的性能开销.bash
dymanic dispatch,是基于运行期的给定信息来肯定调用方法的,可能经过虚函数表实现,也可能借助其余的运行期的信息.app
static dispatch是在编译期就彻底肯定调用方法的分派方式.编程语言
用于在多态状况下,在编译期就实现对于肯定的类型,在函数调用表中推断和追溯正确的方法,包括列举泛型的特定版本,在提供的所有函数定义中选择的特定实现.
在编译器肯定使用static dispatch后,会在生成的可执行文件内,直接指定包含了方法实现内存地址的指针,编译器直接找到相关指令的位置。当函数调用时,系统直接跳转到函数的内存地址执行操做。 这样的好处是,调用指令少,执行快,同时容许编译器可以执行例如内联等优化,缺点是因为缺乏动态性而不支持继承。 事实上,编译期在编译阶段为了可以获取最大的性能提高,都尽可能将函数静态化。
dynamic dispatch是 用于在运行期选择调用方法的实现的流程. dynamic dispatch被普遍应用,而且被认为是面向对象语言的基本特性. OOP是经过名称来查找对象和方法的.可是多态就是一种特殊状况了,由于可能会出现多个同名方法,可是内部实现各不相同.若是把OOP理解为向对象发送消息的话.在多态模式下,就是程序向不知道类型的对象发送了消息,而后在运行期再将消息分派给正确的对象.以后对象再肯定执行什么操做. 与static dispatch在编译期肯定最终执行不一样,dynamic dispatch的目的是为了支持在编译期没法肯定最终最合适的实现的操做.这种状况通常是由于在运行期才能经过一个或多个参数肯定对象的类型.例如 B继承自A, 声明var obj : A = B(),编译期会认为是A类型,可是真正的类型B,只能在运行期肯定.
一种语言可能有多种dynamic dispatch的实现机制.语言的特性不一样,动态分派的实现也各有差别.
消息机制派发 (Message Dispatch): Objc的函数派发都是基于消息派发的。这种机制极具动态性,既能够经过swizzling修改函数的实现,也能够经过isa-swizzling修改对象。
函数表派发是编译型语言实现动态行为最多见的实现方式。 函数表使用了一个数组来存储类声明的每个函数的指针。大部分语言把这个称为 “virtual table”(虚函数表),Swift 里称为 “witness table”。 每个类都会维护一个函数表,里面记录着类全部的函数,若是父类函数被override,表里面只会保存被 override 以后的函数。 一个子类新添加的函数,都会被插入到这个数组的最后。运行时会根据这一个表去决定实际要被调用的函数。
一个函数被调用时会先去读取对象的函数表 再根据类的地址加上该的函数的偏移量获得函数地址 最后跳到那个地址上去。 从编译后的字节码这方面来看就是两次读取一次跳转,比直接派发仍是慢了些。
这种基于数组的实现, 缺陷在于函数表没法拓展. 子类会在虚数函数表的最后插入新的函数, 没有位置可让 extension 安全地插入函数.
编译器能够经过whole module optimization检查继承关系,对某些没有标记final的类经过计算,若是能在编译期肯定执行的方法,则使用Static dispatch。 好比一个函数没有 override,Swift 就可能会使用直接派发的方式,因此若是属性绑定了 KVO 它的 getter 和 setter 方法可能会被优化成直接派发而致使 KVO 的失效,因此记得加上 dynamic 的修饰来保证有效。
编译器内部运行过程分为:语法分析,类型检查,SIL优化,LLVM后端处理。 这是生成sil文件的指令,能够经过查阅SIL官方文档了解文件中各类指令的含义。
swiftc -emit-sil main.swift | xcrun swift-demangle > main.sil
swiftc -emit-silgen -o main.swift | xcrun swift-demangle > main.silgen
源代码:
class A {
let a = 1
var b = 1
private var c = 1
func testA() {
}
}
let a = A()
a.testA()
复制代码
生成的sil
文件:
sil_stage canonical
import Builtin
import Swift
import SwiftShims
class A {
@_hasInitialValue @_hasStorage final let a: Int { get }
@_hasInitialValue @_hasStorage var b: Int { get set }
@_hasInitialValue @_hasStorage private var c: Int { get set }
func testA()
init()
@objc deinit
}
@_hasInitialValue @_hasStorage let a: A { get }
// a
sil_global hidden [let] @main.a : main.A : $A
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @main.a : main.A // id: %2
%3 = global_addr @main.a : main.A : $*A // users: %8, %7
%4 = metatype $@thick A.Type // user: %6
// function_ref A.__allocating_init()
%5 = function_ref @main.A.__allocating_init() -> main.A : $@convention(method) (@thick A.Type) -> @owned A // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick A.Type) -> @owned A // user: %7
store %6 to %3 : $*A // id: %7
%8 = load %3 : $*A // users: %9, %10
%9 = class_method %8 : $A, #A.testA!1 : (A) -> () -> (), $@convention(method) (@guaranteed A) -> () // user: %10
%10 = apply %9(%8) : $@convention(method) (@guaranteed A) -> ()
%11 = integer_literal $Builtin.Int32, 0 // user: %12
%12 = struct $Int32 (%11 : $Builtin.Int32) // user: %13
return %12 : $Int32 // id: %13
} // end sil function 'main'
......
sil_vtable A {
#A.b!getter.1: (A) -> () -> Int : @main.A.b.getter : Swift.Int // A.b.getter
#A.b!setter.1: (A) -> (Int) -> () : @main.A.b.setter : Swift.Int // A.b.setter
#A.b!modify.1: (A) -> () -> () : @main.A.b.modify : Swift.Int // A.b.modify
#A.c!getter.1: (A) -> () -> Int : @main.A.(c in _12232F587A4C5CD8B1EEDF696793A4FC).getter : Swift.Int // A.c.getter
#A.c!setter.1: (A) -> (Int) -> () : @main.A.(c in _12232F587A4C5CD8B1EEDF696793A4FC).setter : Swift.Int // A.c.setter
#A.c!modify.1: (A) -> () -> () : @main.A.(c in _12232F587A4C5CD8B1EEDF696793A4FC).modify : Swift.Int // A.c.modify
#A.testA!1: (A) -> () -> () : @main.A.testA() -> () // A.testA()
#A.init!allocator.1: (A.Type) -> () -> A : @main.A.__allocating_init() -> main.A // A.__allocating_init()
#A.deinit!deallocator.1: @main.A.__deallocating_deinit // A.__deallocating_deinit
}
复制代码
class_method
是经过函数表派发的函数。
sil_vtable A
是A中函数表的内容。
更多详情能够查阅文档。
只声明属性的状况下,会在函数表中生成方法么?有几种方法? 若是var、public、 final等属性修饰后会发生什么事?
static func exchangeFunc() {
let originSelector = #selector(ASViewController.init(nibName:bundle:))
let exchangeSelector = #selector(ASViewController.baiduInit(nibName:bundle:))
guard let m1 = class_getInstanceMethod(self, originSelector) else {
return
}
guard let m2 = class_getInstanceMethod(self, exchangeSelector) else {
return
}
method_exchangeImplementations(m1, m2)
}
复制代码
这个是一个简单的方法交换的示例。 问题:init(nibName:bundle:)是一个初始化方法,为何经过class_getInstanceMethod
去获取而不是class_getClassMethod
? 初始化方法是否有实例方法与类方法的区别?
class A {
static func exchangeFunc() {
let originSelector = #selector(A.printA)
let exchangeSelector = #selector(A.printB)
guard let m1 = class_getInstanceMethod(self, originSelector) else {
return
}
guard let m2 = class_getInstanceMethod(self, exchangeSelector) else {
return
}
method_exchangeImplementations(m1, m2)
}
@objc
func printA() {
print("A")
}
@objc
func printB() {
print("B")
}
}
A.exchangeFunc()
let a = A()
a.printA() // A
复制代码
问题:
为何方法替换的操做在执行后没有生效?
若是但愿生效应该怎么作?
class C1: UIView {
func test1()
}
class C2: C1 {
override func layoutSubview() {
// ...
}
override func test1() {
}
}
复制代码
上面的方法派发机制是什么?
// Defined protocol。
protocol A {
func a() -> Int
}
extension A {
func a() -> Int {
return 0
}
}
// A class doesn't have implement of the function。
class B: A {}
class C: B {
func a() -> Int {
return 1
}
}
B().a()
C().a()
(C() as A).a()
复制代码
分别写出以上方法运行后的输出,以及缘由。
可否在override在extension中定义的方法?
可否在extension中override方法?
小张在工做中实现了相似于第五题的写法。
基类实现一个tableview并继承协议,且实现了其中必须实现的协议。
在子类中,override以及实现的协议或者添加新的tableview协议中的方法。
结果程序正常运行,并无出现bug。
这是为何?
忽然有一天出现了bug,小张修改了Xcode某个属性,程序就能正常运行了。
修改了什么属性?