objc_msgSend的执行流程可以分为3大阶段
消息发送
首先判断receiver消息接收者是否为nil如果为nil退出,如果不为nil,receiver通过isa指针找到receiverClass(消息接收者的类对象,如果消息接收者本来就是类对象,那就找到的是它的元类对象), 从receiverClass(自己类对象)的cache中查找方法,找到方法调用结束,如果当前类对象没有找到方法,就从receiverClass(当前类对象的)class_rw_t(方法列表)中查找方法(如果已经排好序,用二分查找,如果没有排序,线性查找),如果找到了方法,就调用方法,结束查找并将方法缓存到receiverClass的cache中,如果没有找到,就通过receiverClass(当前类对象)的superclass指针找到superClass的cache中查找,如果找到方法,调用方法,结束查找,并将方法缓存到receiverClass的cache中,如果从superClass的cache中没有找到,就从superClass的class_rw_t的方法列表methodList中查找,同样的土改找到调用,结束查找,缓存放到到receiverClass中,如果还没有找到上层已经没有superClass就会到动态解析阶段
void objc_msgSend(id receiver, SEL selector){
if(receiver == nil) return;
// 查找缓存 selector & mask 找到索引,在索引中找到缓存的方法 未写完...
}
动态方法解析
是否曾经有动态解析,如果没有解析才去调用class_resolveInstanceMethod
class_resolveClassMethod解析完成后标记为已经动态解析 转到 正常的消息发送流程,下次就不会到动态方法解析了
开发者可以在这个class_resolveInstanceMethod,class_resolveClassMethod方法中来动态添加方法实现
动态解析过好,会重走"消息发送"流程, 从receiverClass的cache中查找方法,这一步开始执行
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (void)test2 {
NSString *str = NSStringFromSelector(_cmd);
NSLog(@"%@",str);
// _cmd 就相等于@selector(test2)
}
- (void)other {
NSLog(@"%@",__func__);
}
/**
typedef sturct objc_method *Method
struct objc_method == struct method_t
struct method_t *otherMethod = (struct method_t *)class_getInstanceMethod(self, @selector(other));
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 动态添加方法
Method method = class_getInstanceMethod(self, @selector(other));
// @"v16@0:8"
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES; // 返回YES代表有动态添加方法,之后重新走消息发送流程 这是class_rw_t中已经有添加的方法了,所以找到返回
}
return [super resolveInstanceMethod:sel];
}
@end
或者调用C语言函数
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (void)test2 {
NSString *str = NSStringFromSelector(_cmd);
NSLog(@"%@",str);
// _cmd 就相等于@selector(test2)
}
void c_other(id self, SEL _cmd){
NSLog(@"c_other - %@ - %@",self,NSStringFromSelector(_cmd));
}
- (void)other {
NSLog(@"%@",__func__);
}
/**
typedef sturct objc_method *Method
struct objc_method == struct method_t
struct method_t *otherMethod = (struct method_t *)class_getInstanceMethod(self, @selector(other));
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
// 动态添加方法
// Method method = class_getInstanceMethod(self, @selector(other));
// @"v16@0:8"
// class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
class_addMethod(self, sel, (IMP)c_other, @"v16@0:8");
return YES; // 返回YES代表有动态添加方法,之后重新走消息发送流程 这是class_rw_t中已经有添加的方法了,所以找到返回
}
return [super resolveInstanceMethod:sel];
}
@end
消息转发
第一种标记了消息转发为YES,但是没有动态添加方法,最后会调用消息转发,只是标记之后重走消息发送流程时不再走resolveInstanceMethod:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
return YES; // 返回YES代表标记动态解析为true,下次就不会做动态解析,其实本身没有动态添加方法,之后重新走消息发送流程,找不到方法就会进行消息转发
}
return [super resolveInstanceMethod:sel];
}
第二种,也不动态解析,什么都不处理也会调用消息转发
消息转发:将消息转发给别人.
- (id)forwardingTargetForSelector:(SEL)aSelector
返回一个能够处理这个消息的对象
// MMMCat.h
#import <Foundation/Foundation.h>
@interface MMMCat : NSObject
- (void)test;
@end
// MMMCat.m
#import "MMMCat.h"
@implementation MMMCat
- (void)test
{
NSLog(@"调用了猫的test");
}
@end
// MMMPerson.m
#import "MMMPerson.h"
#import <objc/runtime.h>
#import "MMMCat.h"
@implementation MMMPerson
// 将消息转发给MMMCat对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// objc_msgSend([[MMMCat alloc]init], aSelector)
return [[MMMCat alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// VC中调用
- (void)test111 {
MMMPerson *per = [[MMMPerson alloc]init];
[per test];
}
// 打印结果:调用了猫的test
不想将消息转发给别人处理(不实现forwardingTargetForSelector)
//MMMPerson.m
#import "MMMPerson.h"
#import <objc/runtime.h>
#import "MMMCat.h"
@implementation MMMPerson
// 返回了方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// 将方法包装成NSInvocation对象, 自己在这个方法中想做什么就做什么
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"abc------------");
}
@end
// VC中调用
- (void)test111 {
MMMPerson *per = [[MMMPerson alloc]init];
[per test];
}
// 调用之后的打印结果 abc------------
总结
这里调用test干了什么事情就是干了forwardInvocation里面的事情,如果forwardInvocation什么都不做也可以,也可以更改 anInvocation.target,和anInvocation.selector,还可以获取参数值
获取参数值代码
// MMMPerson.h
@interface MMMPerson : NSObject
- (void)test5:(int)age;
@end
//MMMPerson.m
#import "MMMPerson.h"
#import <objc/runtime.h>
#import "MMMCat.h"
@implementation MMMPerson
// 返回了方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test5:)) {
return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"]; // 返回的方法签名决定着forwardInvocation将来包装的参数有多少个,记NSInvocation对象的参数有多少个是由这里的方法签名决定的,所以方法签名不能乱写
}
return [super methodSignatureForSelector:aSelector];
}
// 将方法包装成NSInvocation对象, 自己在这个方法中想做什么就做什么
-(void)forwardInvocation:(NSInvocation *)anInvocation{
int age;
// 参数顺序 receiver selector argument, 所以下标为2
[anInvocation getArgument:&age atIndex:2];
NSLog(@"%d",age); // 结果为10
}
@end
//VC.m中调用
- (void)test111 {
MMMPerson *per = [[MMMPerson alloc]init];
[per test5:10];
}
获取返回值代码
//MMMCat.h
@interface MMMCat : NSObject
- (int)test6:(int)age;
@end
//MMMCat.m
#import "MMMCat.h"
@implementation MMMCat
- (int)test6:(int)age
{
return age * 10;
}
@end
//MMMPerson.h
@interface MMMPerson : NSObject
-(int)test6:(int)age;
@end
//MMMPerson.m
#import "MMMPerson.h"
#import <objc/runtime.h>
#import "MMMCat.h"
@implementation MMMPerson
// 返回了方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test6:)) {
return [NSMethodSignature signatureWithObjCTypes:"i20@0:8i16"];
}
return [super methodSignatureForSelector:aSelector];
}
// 将方法包装成NSInvocation对象, 自己在这个方法中想做什么就做什么
-(void)forwardInvocation:(NSInvocation *)anInvocation{
// 参数顺序 receiver selector argument
// anInvacation.target == person对象
// anInvacation.selector == (int)test6:(int)
// anInvacation的参数 receiver selector 1
[anInvocation invokeWithTarget:[[MMMCat alloc]init]];
// 这时候 anInvacation.target == [[MMMCat alloc]init]
// 即re = [[[MMMCat alloc]init] test6:1]
int re;
[anInvocation getReturnValue:&re];
NSLog(@"%d",re); // 打印结果为10
}
@end
//vc.m
- (void)test111 {
MMMPerson *per = [[MMMPerson alloc]init];
[per test6:1];
}
消息查找阶段
很多人称为消息发送阶段,我自己更习惯称消息的查找阶段。在Class的结构及方法缓存中说过,方法的调用会先去类的缓存列表区查找函数的实现,从上面的源码也证实了这一点,如果在类的缓存中找到函数实现直接返回该实现。
如果缓存中没有,接着去类的方法列表中查找,找后缓存到类中后返回该函数。
如果类方法列表也没有找到,接着去循环查询父类的缓存和方法列表,直至找NSObject为止。同样的,找后缓存到类中后返回该函数。如果都没有找到对应的方法实现,将进入第二个阶段,消息的动态解析;
消息动态解析阶段
之所以叫动态解析阶段,是因为我们可以在这个阶段为类或元类添加目标方法的实现。首先调用函数_class_resolveMethod
根据cls是类还是元类(也即是调用的是实例方法还是类方法)分别调用函数_class_resolveInstanceMethod、_class_resolveClassMethod添加目标方法的实现。这两个函数的实现大致相同,贴出_class_resolveInstanceMethod的源码:
如果没有在方法resolveInstanceMethod中为类添加对应的方法实现函数直接return。不论是否填加了实现,都会把结果缓存起来,避免以后调用再次触发方法的动态解析。如果在动态方法解析阶段,也没有为类添加方法实现,最后将进入
消息转发阶段;将消息转发给别人
首先调用forwardingTargetForSelector方法,我们可以在这个方法里返回一个实现了目标SEL的对象,那么最终会调用这个对象的方法实现。如果返回的对象没有实现目标函数,仍会报“unrecognized selector …”的错误。
如果forwardingTargetForSelector中没有做处理,接着会调用methodSignatureForSelector方法,我们可以在这个方法里返回一个方法签名,返回方法签名后会调用forwardInvocation方法,在forwardInvocation方法里,我们可以任意的实现目标方法的调用。只要methodSignatureForSelector方法里返回了方法签名,forwardInvocation中的实现,我们可以任意处理,甚至不做任何处理程序都不会崩溃。
OC消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名),
objc_msgSend底层3大阶段
消息发送(当前类,父类中查找), 动态方法解析,消息转发