编译型语言和非编译型语言
-
编译型语言:需要将高级语言(比如C/OC)需要通过编译器转换成平台(比如Windows/iOS/Android)能够理解的机器代码(汇编或者说二进制,因为不同系统架构的命令集不一样);比如对 C 语言或者其他编译型语言来说,编译生成了目标文件,而这个目标文件是针对特定的 CPU 体系的,为 ARM 生成的目标文件,不能被用于 MIPS 的 CPU。这段代码在编译过程中就已经被翻译成了目标 CPU 指令,所以,如果这个程序需要在另外一种 CPU 上面运行,这个代码就必须重新编译
-
非编译型语言:对于各种非编译型语言(例如python/java)来说,同样也可能存在某种编译过程,但他们编译生成的通常是一种『平台无关』的中间代码,这种代码一般不针对特定的 CPU 平台,他们是在运行过程中才被翻译成目标 CPU 指令的,因而,在 ARM CPU 上能执行,换到 MIPS 也能执行,换到 X86 也能执行,不需要重新对源代码进行编译
编译型语言的函数派发方式:
直接(静态)派发(Direct Dispatch)、函数表派发(Table Dispatch)、消息机制派发(Message Dispatch)。
直接(静态)派发 (Direct Dispatch):
直接派发也叫静态派发。在直接派发中,编译器直接找到相关指令的位置。当函数调用时,系统直接跳转到函数的内存地址执行操作。这样的好处就是执行快,同时允许编译器能够执行例如内联等优化。事实上,编译期在编译阶段为了能够获取最大的性能提升,都尽量将函数静态化。
函数表派发 (Table Dispatch):
函数表派发是编译型语言实现动态行为最常见的实现方式。函数表使用了一个数组来存储类声明的每一个函数的指针。大部分语言把这个称为 “virtual table”(虚函数表),Swift 里称为 “witness table”。每一个类都会维护一个函数表,里面记录着类所有的函数,如果父类函数被 override 的话,表里面只会保存被 override 之后的函数。一个子类新添加的函数,都会被插入到这个数组的最后。运行时会根据这一个表去决定实际要被调用的函数。
消息机制派发 (Message Dispatch):
Objc的函数派发都是基于消息派发的。这种机制极具动态性,既可以通过swizzling修改函数的实现,也可以通过isa-swizzling修改对象。
Swift 的派发机制
Swift 没有在文档里具体写明什么时候会使用函数表什么时候使用消息机制。唯一的承诺是使用 dynamic修饰的时候会通过 Objective-C 的运行时进行消息机制派发。
默认情况:
Swift函数声明位置有两种:
- 类、结构体、枚举和协议的声明位置;
- 它们的extension位置。
默认情况下,Swift 使用的派发方式总结起来有这么几点:
- 值类型、协议的extension、类的extension总是会使用直接派发;
- NSObject 的 extension 会使用消息机制进行派发;
- NSObject 声明作用域里的函数都会使用函数表进行派发;
- 协议里声明的, 并且带有默认实现的函数会使用函数表进行派发。
关键字指定派发方式:
-
final:允许类里面的函数使用直接派发,这个修饰符会让函数失去动态性。任何函数都可以使用这个修饰符,就算是 extension 里本来就是直接派发的函数。这也会让 Objective-C 的运行时获取不到这个函数,不会生成相应的 selector。
-
dynamic:可以让类里面的函数使用消息机制派发。使用 dynamic必须导入 Foundation 框架,里面包括了 NSObject 和 Objective-C 的运行时。dynamic 可以让声明在 extension 里面的函数能够被 override。dynamic 可以用在所有 NSObject 的子类和 Swift 的原声类。
-
@objc 或 @nonobjc:都可以显式地声明一个函数是否能被 Objective-C 的运行时捕获到。但使用 @objc 的典型例子就是给 selector 一个命名空间 @objc(abc_methodName),让这个函数可以被 Objective-C 的运行时调用。@nonobjc会改变派发的方式,可以用来禁止消息机制派发这个函数,不让这个函数注册到 Objective-C 的运行时里。
-
final 与 @objc同时使用:可以在标记为 final 的同时,也使用 @objc 来让函数可以使用消息机制派发。这么做的结果就是,调用函数的时候会使用直接派发,但也会在 Objective-C 的运行时里注册响应的 selector。函数可以响应 perform(selector:) 以及别的 Objective-C 特性,但在调用时又可以有直接派发的性能。
-
@inline:Swift 也支持 @inline,告诉编译器可以使用直接派发。有趣的是,dynamic @inline(__always) func dynamicOrDirect() {} 也可以通过编译!但这也只是告诉了编译器而已,实际上这个函数还是会使用消息机制派发。这样的写法看起来像是一个未定义的行为,应该避免这么做。