动态调度与目标C运行时
在Object-C中,程序由一组对象组成,这些对象通过传递消息来相互交互,而消息又反过来调用。方法或功能。这种消息传递行为由方括号语法表示:
[someObject aMethod:withAnArgument];
在编译Object-C代码时,将消息发送转换为调用一个名为objc_msgSend(字面上“向带有参数的对象发送消息”).
objc_msgSend(object, @selector(message), withAnArgument);
- 第一个参数是接收方(
self
例如方法) - 第二个论点是
_cmd*
选择器或方法的名称 - 任何方法参数都作为附加函数参数传递。
objc_msgSend
负责确定响应此消息调用哪个底层实现,该流程称为方法调度.
在目标C中,每个类(Class
)维护一个调度表来解析在运行时发送的消息。调度表中的每个条目都是一个方法(Method)键选择器(SEL
)相应的实现(IMP
),它是指向C函数的指针。当一个对象收到消息时,它会查询其类的调度表。如果可以找到选择器的实现,则调用关联的函数。否则,对象引用其超类的调度表。这继续在继承链上进行,直到找到匹配项或根类(NSObject
)认为选择器不被识别。
如果您的代码中有一条热门路径,这是一种频繁调用的昂贵方法,那么您可以想象避免所有这些间接操作的好处。为此,一些开发人员使用C函数作为一种绕过动态分派的方法。
具有C函数的直接调度
就像我们看到的objc_msgSend
,任何方法调用都可以通过传递隐式表示为等效函数。self
作为第一个论点。
例如,考虑使用常规的动态分派方法的Object-C类声明。
@interface MyClass: NSObject
- (void)dynamicMethod;
@end
如果开发人员希望在MyClass
不需要查看发送shebang的整个消息,他们就可以声明一个静态C函数,该函数的实例是MyClass
作为一种争论。
static void directFunction(MyClass *__unsafe_unretained object);
下面是这些方法中的每一种方法如何转换到呼叫站点:
MyClass *object = [[[MyClass] alloc] init];
// Dynamic Dispatch
[object dynamicMethod];
// Direct Dispatch
directFunction(object);
直接法
A 直接法具有传统方法的外观和感觉,但具有C函数的行为。当一个直接方法被调用时,它直接调用它的底层实现,而不是遍历。objc_msgSend
使用这个新的LLVM修补程序,您现在可以对Object-C方法进行注释,以避免有选择地参与动态分派。
objc_directive、@Property(直接)和objc_Direct_Members
若要直接使用实例或类方法,可以将其标记为objc_direct`` clang属性
...同样,通过使用direct属性属性
@interface MyClass: NSObject
@property(nonatomic) BOOL dynamicProperty;
@property(nonatomic, direct) BOOL directProperty;
- (void)dynamicMethod;
- (void)directMethod __attribute__((objc_direct));
@end
根据我们的统计,direct使.的总数
@property
属性为16:
getter
和setter
readwrite
和readonly
,atomic
和nonatomic
weak
,strong
copy
,retain
,和unsafe_unretained
nullable
,nonnullable
,和null_resettable
class
当@interface
对于类别或类扩展,则使用objc_direct_members
属性中包含的所有方法和属性声明都被认为是直接的,除非该类先前声明过。
控件不能对主类接口进行注释。objc_direct_members属性。
__attribute__((objc_direct_members))
@interface MyClass ()
@property (nonatomic) BOOL directExtensionProperty;
- (void)directExtensionMethod;
@end
注释@implementation
带着objc_direct_members
具有类似的效果,导致非先前声明的成员被认为是直接的,包括任何由属性合成产生的隐式方法。
__attribute__((objc_direct_members))
@implementation MyClass
- (BOOL)directProperty {…}
- (void)dynamicMethod {…}
- (void)directMethod {…}
- (void)directExtensionMethod {…}
- (void)directImplementationMethod {…}
@end
动态方法不能被直接方法在子类中重写,直接方法根本不能被重写。
协议不能声明直接方法需求,类不能用直接方法实现协议需求。
将这些注释应用到前面的示例中,我们可以看到调用站点上的直接方法和动态方法是如何难以区分的:
MyClass *object = [[[MyClass] alloc] init];
// Dynamic Dispatch
[object dynamicMethod];
// Direct Dispatch
[object directMethod];
对于我们中注重性能的开发人员来说,直接方法似乎是一个扣人心弦的特性。但有个转折:
在大多数情况下,直接使用方法可能不会有明显的性能优势。
隐藏动机
当目标-C方法被标记为直接时,它的实现具有隐藏的可见性。也就是说,直接方法只能在同一个模块中调用。(或者是学究,联动单元).它甚至不会出现在Object-C运行时。
隐藏的能见度有两个直接的优点:
- 较小的二进制尺寸
- 没有外部调用
没有外部可见性或从Object-C运行时动态调用它们的方法,直接方法实际上是私有方法。
如果您希望参与直接分派,但仍然希望使您的API在外部可访问,则可以将其包装在一个C函数中。
static inline void performDirectMethod(MyClass *__unsafe_unretained object) { [object directMethod]; }
另外,如果你想一起进阶,不妨添加一下交流群1012951431,选择加入一起交流,一起学习。期待你的加入!