objc_msgSend
调用一个方法的时候,runtime
层会将这个调用翻译成
objc_msgSend(id self, SEL op, ...)
比如,一条语句 [receiver message];
会由编译器转化为以下的纯 C
调用:
objc_msgSend(receiver, @selector(message));
objc_msgSend
的伪代码为:
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_stret
或 objc_msgSendSuper_stret
。
消息分发
objc_msgSend
的消息分发分为以下几个步骤:
- 判断
receiver
是否为nil
,也就是objc_msgSend
的第一个参数self
,也就是要调用的那个方法所属对象 - 从缓存里寻找,找到了则分发,否则
- 利用
objc-class.mm
中_class_lookupMethodAndLoadCache3
方法去寻找selector
- 如果支持
GC
,忽略掉非GC
环境的方法(retain
等) - 从本
class
的method list
寻找selector
,如果找到,填充到缓存中,并返回selector
,否则 - 寻找父类的
method list
,并依次往上寻找,直到找到selector
,填充到缓存中,并返回selector
,否则 - 调用
_class_resolveMethod
,如果可以动态resolve
一个selector
,不缓存,方法返回,否则 - 转发这个
selector
,否则
- 如果支持
- 报错,抛出异常
类的定义里就有 cache
字段,类的所有缓存都存在 metaclass
上,所以每个类都只有一份方法缓存,而不是每一个类的 object
都保存一份。
即便是从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的 metaclass
里缓存一份。
在调用 _class_lookupMethodAndLoadCache3
之前,已经是从缓存无法找到 selector
了,所以这个方法避免了再去扫描缓存查找方法的过程,而是直接从方法列表找起。
动态添加方法
允许用户在此时为该 Class
动态添加实现。
void _class_resolveMethod(Class cls, SEL sel, id inst)
函数原码为:
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
方法。
动态添加实例方法:
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
动态添加类方法:
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
中。关于 class
和 meta class
的关系可以参考下图:
消息转发机制(message forwarding)
当对象接受到无法解读的消息后,就会启动消息转发机制。当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消息转发。
消息转发分为两个阶段
- 先征询接收者所属都类,看其是否能动态添加方法
- 完整的消息转发机制
- 查看是否有备援接收者
replacement receiver
,否则 - 把消息的全部细节封装到
NSInvocation
对象中,再给接收者最后一次机会
- 查看是否有备援接收者
备援接收者
尝试找到一个能响应该消息的对象。
- (id)forwardingTargetForSelector:(SEL)selector;
完整的消息转发
先调用 methodSignatureForSelector:
方法,尝试获得一个方法签名。如果获取不到,则直接调用 doesNotRecognizeSelector
抛出异常。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
然后创建 NSInvocation
对象,调用
- (void)forwardInvocation:(NSInvocation *)invocation;
代码:
- (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];
}
}