前言

系列第三篇:来看看OC的消息。包括方法查找、缓存、方法决议以及消息转发

objc_msgSend

objc_msgSend 函数的原型为 void objc_msgSend(id self, SEL cmd, ...)

给一个对象发送消息:

1
id returnValue = [someObject messageName:parameter];

编译器会将该OC语句转换成objc_msgSend函数:

1
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

其中的SEL选择子可以理解成方法的名字。

从这里可以看出,给OC对象发送消息,其实就是调用objc_msgSend函数,所有这篇文章的重点也是该函数的具体实现过程

OC方法本质

1
2
3
4
5
- (void)hello:(int)a;

// 该方法编译的时候会被转化成:

void hello(id self, SEL _cmd, int a);

当有一个对象调用该方法时:

1
2
3
4
5
6
7
[someObj hello:2];

// 编译之后就是
objc_msgSend(someObj, @selector(hello:), 2);

// 当objc_msgSend找到方法的实现,也就是hello方法时,相当于调用:
hello(someObj, @selector(hello:), 2);

方法查找 && 缓存

回忆一下objc_class结构体

objc_class

类的方法都存放在bits成员变量中,而方法的缓存则是存放在cache成员变量中。

objc_msgSend函数通过调用lookUpImpOrForward函数来查找方法的实现

当前类查询

objc_msgSend函数查询方法的实现,先会在当前类中查询。

  1. 先在缓存cache中寻找是否存在该方法,找到就去执行
  2. 如果没在缓存中找到该方法,就到bits中寻找方法。如果找到,会先写入缓存cache,再去执行方法;如果没找到,就会去super_class里面寻找

沿着类继承关系查询

每个objc_class都会有指向父类的指针。当子类无法提供方法的实现时,会跑到父类来寻找该实现,过程相似:

  1. 在父类的缓存cache中寻找。如果找到,写到调用该方法的类的cache中,再执行
  2. 在缓存中没找到,就继续在父类的bits中查找,找到,写入缓存并执行
  3. 如果还是没找到,继续沿着类继承关系寻找方法的实现

方法决议

+ (BOOL)resolveInstanceMethod:(SEL)selector

+ (BOOL)resolveClassMethod:(SEL)selector

如果在当前类以及所有父类之中都无法找到方法的实现的话,就进入了方法决议的阶段。来看看方法决议的过程:(该过程的源码也在lookUpImpOrForward方法中)

  1. 查看当前类是否实现了resolve方法。如果没有,决议直接失败

  2. 如果存在resolve方法,那么执行该方法。这个方法里面一般会动态给当前类添加方法,是的之前没有找到实现的方法可以实现。例如,来实现@dynamic的属性的存取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 注意:以下两方法的相关实现代码已经写好
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);

+ (BOOL)resolveInstanceMethod:(SEL)selector {
NSString *selectorString = NSStringFromSelector(selector);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
} else {
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
}

return YES;
}

在这一个resolve方法里面,给类新增加了一个方法。

  1. 执行完resolve方法之后,会重新进行一次方法的查找。如果找到方法了,执行

消息转发

如果方法决议也没有用的话,会给这个方法最后一个机会:消息转发

备援接收者

- (id)forwardingTargetForSelector:(SEL)selector;

执行该函数,看能否返回一个新的对象,这个对象可以处理该方法。具体参见官方文档

完整的消息转发

如果无法找到备胎的话,就要进行完整的消息转发了。将未处理的消息封装成NSInvocation对象,然后调用- (void)forwardInvocation:(NSInvocation *)invocation; 方法。

这个转发的方法属于 NSObject,需要子类重写该方法。具体参见官方文档

完整的消息转发图示

forwarding

其他

在后续的一片文章中会有详细的示例来说明消息决议和转发

参考