OC的消息转发流程
Swift消息的三种派发机制
直接派发(Direct Dispatch)
- 直接派发是最快的,不止是因为需要调用的指令集会更少,并且编译器还能够有很大的优化空间,例如函数内联等,直接派发也有人称为静态调用。
- 然而,对于编程来说直接调用也是最大的局限,而且因为缺乏动态性所以没办法支持继承和多态。
函数表派发(Table Dispatch)
- 函数表派发是编译型语言实现动态行为最常见的实现方式函数表使用了一个数组来存储类声明的每一个函数的指针.大部分语言把这个称为virtual table"虚函数表), Swift里称为“witness table.每一个类都会维护一个函数表,里面记录着类所有的函数,如果父类函数被 override的话,表里面只会保存被 override之后的函数.一个子类新添加的函数,都会被插入到这个数组的最后运行时会根据这一个表去决定实际要被调用的函数
- 查表是一种简单,易实现,而且性能可预知的方式.然而,这种派发方式比起直接派发还是慢一点从字节码角度来看,多了两次读和一次跳转,由此带来了性能的损耗.另一个慢的原因在于编译器可能会由于函数内执行的任务导致无法优化(如果函数带有副作用的话)
- 这种基于数组的实现,缺陷在于函数表无法拓展.子类会在虚数函数表的最后插入新的函数,没有位置可以让 extension安全地插入函数.
消息机制派发(Message Dispatch)
- 消息机制是调用函数最动态的方式.也是Ccoa的基石,这样的机制催生了kvo, UIAppearence和 CoreData等功能.这种运作方式的关键在于开发者可以在运行时改变函数的行为.不止可以通过 swizzling来改变,甚至可以用 isa-swizzling修改对象的继承关系可以在面向对象的基础上实现自定义派发.
Swift运行时
- 纯 Swift类的函数调用已经不再是 Objective-c的行时发消息,而是类似C++的vtable,在编译时就确定了调用哪个函数,所以没法通过 runtime获取方法、属性。
- 而Swift为了兼容 Objective-C,凡是继承自 NSObjec的类都会保留其动态性,所以我们能通过 runtime拿到他的方法。这里有一点说明:老版本的 Swift(如2.2)是编译期隐式的自动帮你加上了@objc,而40以后版本的 Swift编译期去掉了隐式特性,必须使用显式添加。
- 不管是纯Swift类还是继承自 NSObject的类只要在属性和方法前面添加@objc关键字就可以使用 runtime.
项目 | 原始定义 | 扩展 |
值类型 | 直接派发 | 直接派发 |
协议 | 函数表派发 | 直接派发 |
类 | 函数表派发 | 直接派发 |
继承自NSObject的类 | 函数表派发 | 消息机制派发 |
小结
- 值类型总是会使用直接派发,简单易懂
- 而协议和类的 extension都会使用直接派发
- NSObject的 extension会使用消息机制进行派发
- NSObject声明作用域里的函数都会使用函数表进行派发.
- 协议里声明的,并且带有默认实现的函数会使用函数表进行派发
一些关键字的派发机制
关键字 | 机制 |
final | 直接派发 |
dynamic | 消息机制派发 |
@objc & @nonobjc | 改变在OC里的可见性 |
@inline | 告诉编译器可以直接派发 |
Swift 运行时-final @objc
- 可以在标记为 final的同时,也使用@objc让函数可以使用消息机制派发.这么做的结果就是,调用函数的时候会使用直接派发,但也会在 Objective-c的运行时里注册响应的 selector.函数可以响应 perform(selector)以及别的 Objective-C特性但在直接调用时又可以有直接派发的性能.