Objective-C Direct Methods
commit :
Implement attribute((objc_direct)), attribute((objc_direct_me… · llvm/llvm-project@d4e1ba3
Clang 13 新增了 objc_direct
声明,对应的 Xcode 版本为 12 。使用 objc_direct
标记的 Objective-C 方法为直接派发方法。 direct
方法在调用时类似于静态方法,降低了一定的性能损耗,但是也牺牲了 Objective-C 的一些特性。
direct
方法的消息发送会像 C 函数那样直接调用实现,没有走 Objective-C 的消息发送流程。这里速度会比较快和有可能进行函数内联,这同时意味着方法不能够被重写或者动态替换。因此 direct
方法不会出现在 class
的方法列表中,可以减少代码段的大小。虽然说 direct
方法会当做 C 函数来进行静态调用,但是它仍然遵循以下 Objective-C 语义:
- 如果接收者为
nil
,则不会进行任何处理和返回方法返回值中对应的zero
值; - 调用
direct
方法也会触发class
的初始化,包括调用+initiablize
方法; - 保留方法选择器的隐式参数
_cmd
,但是如果_cmd
参数没有在方法中进行调用,那么实现中将不会保留对_cmd
的引用,以减少代码段的大小。
同时不支持以下操作:
- 在
protocol
中声明direct
方法; - 在子类重写
direct
方法; - 在子类中重写非
direct
方法,同时定义为direct
; - 相同的方法在不同的类接口中对
direct
的声明不一致; - 在
interface
和implement
中对direct
的声明不一致; - 不可以通过
@selector
和方法名来获取direct
方法的SEL
。
class
的 interface
包括了 class
的 @interface
block ,extensions , categories ,protocol 和父类的 interface
。
Objective-C 属性也支持声明为 direct
属性,在没有显示声明 getter
和 setter
的前提下,会默认声明为 direct
。
如果需要批量声明,可以使用 objc_direct_members
声明。可以在 @interface
或者 @implementation
中进行声明,对应的 .m
中的方法都会被设为 direct
。当 objc_direct_members
放置在 @interface
时,只有出现在 @interface
的方法会设置为 direct
,这包括了属性的隐式方法声明。
如果在 @implementation
中进行声明,除了已在其它 @interface
声明为 non-direct
的方法,其它方法都会声明为 direct
。
如果没办法保证外部调用会如何处理方法,那么比较保守的策略就是只处理 .m
里的私有方法和属性。
这条推下面详细说明了 Objective-C 消息发送机制带来的损耗,主要包括以下四方面:
- 代码段大小,由于需要查找方法指针
IMP
,所以需要传递sekf
和_cmd
参数。每次调用会带来额外 8 字节的消耗,由于objc_msgSend
的调用非常频繁,所以 8 字节叠加后占比非常大。在 CloudKit 中,这两个指令占了__text
段 10.7% 的大小; - 优化阻碍,为了更强的动态性和 ARC 等特性, Objective-C 要求编译器做出巨大的保护性编程。拒绝内联是比较明显的一个问题,还有的是在获取可读的
integer
属性时也会带来影响,ARC 会插入objc_retain()/objc_releas()
方法包围属性的getter
方法; - 静态元数据,每个 Objective-C 的方法都会增加 150-250 字节大小。
- 类的方法列表中会增加 24 字节;
- 方法的
type
字符串超过 60 字节,如果不使用NSInvocation
则没有不需要该字符串; - 方法的选择器平均为 20+ 字节。
- 运行时元数据,为了加快消息发送的速度,运行时会创建
IMP
缓存,即使是一些只调用一次的方法,也会带进程带来负担,同时由于运行时初始化时需要重定位指针,也会对启动性能带来影响。
大多数情况下, direct
方法其实不会带来明显的性能收益, objc_msgSend
的处理速度其实非常快,这得益于极其侵略的缓存策略,广泛的底层优化以及现代处理器的进步。在存量代码比较多的情况下,把方法改写为 direct
会减少一定的代码段大小。
性能相关数据:
PSPDFKit 替换了 3717 个方法,减少了 700 KB ,平均每个方法减少了 200 字节。
建议只在内部属性或者方法中使用,在公开的属性或者方法中使用时,如果有新开启 direct
,上层有调用相关接口的二进制库都需要重新编译。†
Comments powered by Disqus.