消息发送与转发

本文总结了 runtime 中的消息发送与转发过程。

objc_msgSend

调用一个方法的时候,runtime 层会将这个调用翻译成

1
objc_msgSend(id self, SEL op, ...)

比如,一条语句 [receiver message]; 会由编译器转化为以下的纯 C 调用:

1
objc_msgSend(receiver, @selector(message));

objc_msgSend 的伪代码为:

1
2
3
4
5
6
7
id objc_msgSend(id self, SEL _cmd, ...) {
Class c = object_getClass(self);
IMP imp = cache_lookup(c, _cmd);
if(!imp)
imp = class_getMethodImplementation(c, _cmd);
return imp(self, _cmd, ...);
}

当向一般对象发送消息时,调用 objc_msgSend;当向 super 发送消息时,调用的是 objc_msgSendSuper; 如果返回值是一个结构体,则会调用 objc_msgSend_stretobjc_msgSendSuper_stret

消息分发

objc_msgSend 的消息分发分为以下几个步骤:

  • 判断 receiver 是否为 nil,也就是 objc_msgSend 的第一个参数 self,也就是要调用的那个方法所属对象
  • 从缓存里寻找,找到了则分发,否则
  • 利用 objc-class.mm_class_lookupMethodAndLoadCache3 方法去寻找 selector
    • 如果支持 GC,忽略掉非 GC 环境的方法(retain 等)
    • 从本 classmethod list 寻找 selector,如果找到,填充到缓存中,并返回 selector,否则
    • 寻找父类的 method list,并依次往上寻找,直到找到 selector,填充到缓存中,并返回 selector,否则
    • 调用 _class_resolveMethod,如果可以动态 resolve 一个 selector,不缓存,方法返回,否则
    • 转发这个 selector,否则
  • 报错,抛出异常

类的定义里就有 cache 字段,类的所有缓存都存在 metaclass 上,所以每个类都只有一份方法缓存,而不是每一个类的 object 都保存一份。
即便是从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的 metaclass 里缓存一份。
在调用 _class_lookupMethodAndLoadCache3 之前,已经是从缓存无法找到 selector 了,所以这个方法避免了再去扫描缓存查找方法的过程,而是直接从方法列表找起。

动态添加方法

允许用户在此时为该 Class 动态添加实现。

1
void _class_resolveMethod(Class cls, SEL sel, id inst)

函数原码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}

此方法是动态方法解析的入口,会间接地发送 +resolveInstanceMethod+resolveClassMethod 消息。通过对 isa 指向的判断,从而分辨出如果是对象方法,则进入 +resolveInstanceMethod 方法,如果是类方法,则进入 +resolveClassMethod 方法。

动态添加实例方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
void dynamicInstanceMethod(id self, SEL _cmd) {
NSLog(@" >> dynamic Instance Method");
}
@implementation TestMessage
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(noneMethod))
{
class_addMethod([self class], sel, (IMP)dynamicInstanceMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end

动态添加类方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void dynamicClassMethod(id self, SEL _cmd){
NSLog(@" >> dynamic Class Method");
}
@implementation TestMessage
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(noneMethod)) {
Class metaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);
class_addMethod(metaClass, @selector(noneMethod), (IMP)dynamicClassMethod, "v@:");
return YES;
}
else{
return [super resolveClassMethod:sel];
}
}
@end

注意类方法需要添加到 Meta Class 中。关于 classmeta class 的关系可以参考下图:

消息转发机制(message forwarding)

当对象接受到无法解读的消息后,就会启动消息转发机制。当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward 会尝试做消息转发。

消息转发分为两个阶段

  • 先征询接收者所属都类,看其是否能动态添加方法
  • 完整的消息转发机制
    • 查看是否有备援接收者 replacement receiver,否则
    • 把消息的全部细节封装到 NSInvocation 对象中,再给接收者最后一次机会

备援接收者

尝试找到一个能响应该消息的对象。

1
- (id)forwardingTargetForSelector:(SEL)selector;

完整的消息转发

先调用 methodSignatureForSelector: 方法,尝试获得一个方法签名。如果获取不到,则直接调用 doesNotRecognizeSelector 抛出异常。

1
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

然后创建 NSInvocation 对象,调用

1
- (void)forwardInvocation:(NSInvocation *)invocation;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(noneMethod)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
MyClass *myclass = [MyClass new];
if ([myclass respondsToSelector:selector]) {
[anInvocation invokeWithTarget:myclass];
}
}

消息转发总结

非常值得读的文章