Runtime之objc_msgSend执行流程

1、objc_msgSend本质

在OC中,方法调用其实就是转换成objc_msgSend函数的调用。
发送message只需要指定 对象 和 SEL ,Runtime的objc_msgSend会根据信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用。

MJPerson *peron = [[MJPerson alloc] init];
    
[peron personTest];
//编译后.cpp文件:
//((void (*)(id, SEL))(void *)objc_msgSend)((id)peron, sel_registerName("personTest"));
//消息接受者(receiver):  peron
//消息名称:  “personTest”
    
    
[MJPerson initialize];
//编译后.cpp文件:
// ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("initialize"));
//消息接受者(receiver):  objc_getClass("MJPerson"),  是MJPerson这个类,而不是实例对象
//消息名称:  initialize
    
    
NSLog(@"%p",sel_registerName("personTest"));
NSLog(@"%p",@selector(personTest));
//以上两句打印的地址是一样的,这说明方法名一样,就是一个东西
//只是编译器把iOS的@selector()转成了C的sel_registerName()这样的方法

objc_msgSend的执行流程可以分为3大阶段:
消息发送 -> 动态方法解析 -> 消息转发
下面,我们来详细说明一下:

2、消息发送
消息发送流程
  • 检查receiver是否有效,有效则开始查找方法;
  • 先从receiverClass的cache中查找方法,找得到就跳到对应的函数去执行,如果cache中找不到就去receiverClass的class_rw_t中去查找;
  • 如果还是找不到就依次去超类的cache中、class_rw_t中查找,直到找到NSObject类为止
  • 如果还找不到就进入动态方法解析。
3、动态方法解析
动态方法解析流程
#import "MJPerson.h"
#import <objc/runtime.h>


//method_t的结构如下
//可以利用method_t来定义方法,并且otherMethod->imp、otherMethod->types的方式取得imp、types、name
struct method_t {
    SEL name;
    char *types;
    IMP imp;
};

@implementation MJPerson

//方法1:使用结构体method_t来定义方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personTest)) {
        //获取其他方法
        struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
       //动态添加方法的实现
        class_addMethod(self, sel, otherMethod->imp , otherMethod->types);
        //返回YES
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}



//方法2:使用Method来定义方法,需要通过API取得方法的imp、types、name
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personTest)) {
        //动态添加方法的实现
        Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
        //动态添加test方法的实现
        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}



//方法3:使用C语言函数
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personTest)) {
        //动态添加方法的实现
        class_addMethod(self, sel, (IMP)c_otherTest, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

-(void)otherTest{
    NSLog(@"Person otherTest");
}

void c_otherTest(id self,SEL _cmd){
    NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
@end

上面代码中的resolveInstanceMethod是用来动态解析实例方法的,
类方法是用resolveClassMethod来动态解析的。
对于类方法,基本和实例方法一样,只是需要实现resolveClassMethod方法;另外在class_addMethod中需要用object_getClass(self)取得类对象,对类对象进行添加方法操作;

struct method_t {
    SEL name;
    char *types;
    IMP imp;
};

//方法1:使用结构体method_t来定义方法
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(PersonClassTest)) {
        //获取其他方法
        struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
        
        //因为是MJPerosn这个类去调用,要用object_getClass(self)
        class_addMethod(object_getClass(self), sel, otherMethod->imp , otherMethod->types);
        //返回YES
        return YES;
    }
    return [super resolveClassMethod:sel];
}


//方法2:使用Method来定义方法,需要通过API取得方法的imp、types、name
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(PersonClassTest)) {
        Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
       
        //因为是MJPerosn这个类去调用,要用object_getClass(self)
        class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


//方法3:使用C语言函数
+ (BOOL)resolveClassMethod:(SEL)sel{
    if (sel == @selector(PersonClassTest)) {
        //因为是MJPerosn这个类去调用,要用object_getClass(self)
        class_addMethod(object_getClass(self), sel, (IMP)c_otherTest, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}


-(void)otherTest{
    NSLog(@"Person cl otherTest");
}


void c_otherTest(id self,SEL _cmd){
    NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
  • 如果事先没有动态解析,那便会通过resolveInstanceMethod / resolveClassMethod方法,对receiver动态添加方法,然后再进入消息发送;
  • 如果之前已经有动态方法解析了,那进入消息转发。
4、消息转发

消息转发流程

forwardingTargetForSelector方法

//程序中调用personTest
MJPerson *person = [[MJPerson alloc] init];
[person personTest];


//在person.m文件中实现:
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(personTest)) {
        //返回MJCat,要求在MJCat中实现personTest方法
        //就相当于调用了objc_msgSend([[MJCat alloc] init],aSelector);
        return [[MJCat alloc] init];
        
        //通过建立分类,在NSObject分类中实现personTest方法,也可以成功实现消息转发
        //return [[NSObject alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

消息转发,如果forwardingTargetForSelector这个方法没有实现 或者 这个方法return nil的话。这种情况下,会调用methodSignatureForSelector方法

#pragma mark -无参的无返回值的可以用v@:  不一定非要写数字

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(personTest)) {
        //返回方法签名:包含返回值类型、参数类型
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
       
        //也可以使用下面这句代替,这样就不用自己管Type Encodings了
        //return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

//    NSInvocation封装了一个方法调用:包含方法调用者、方法名、方法参数
//    anInvocation.target   --->  方法调用者
//    anInvocation.selector   --->   方法名字
//    [anInvocation getArgument:NULL atIndex:0]  --->  方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"可以在这个方法中实现你想做的操作");
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];    
}



#pragma mark -对于有参,有返回值的,anInvocation中也有更多的功能
//ageTest:方法是传入int类型的age,然后乘以2再返回

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(ageTest:)) {
        return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
       
        //也可以使用下面这句代替,这样就不用自己管Type Encodings了
        //return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
    int ret;
    [anInvocation getReturnValue:&ret]; //getReturnValue是获取方法返回值,anInvocation中还有其他更多的数据
    NSLog(@"打印结果 %d",ret);
}

对于类方法来说,消息转发时,需要有点改变:就是要将调用的方法改成+开头,也就是类方法;另外消息接受者为类,而不是实例对象。

#pragma mark -   - (id)forwardingTargetForSelector:(SEL)aSelector对应的
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(personTest)) {
        //因为是调用类方法,所以这里要注意一下
        return [MJCat class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

当没有实现forwardingTargetForSelector方法或者这个方法返回nil时,methodSignatureForSelector方法,注意都是类方法,带+号的。

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(PersonClassTest)){
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    //这里调用[MJCat class]这个类
    [anInvocation invokeWithTarget:[MJCat class]];
}
5、总结
  • <1> 介绍一下OC的消息机制
    OC中的方法调用都是转成obje_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
    objc_msgSend底层有三大阶段:
    (1)消息发送(在当前类、父类中查找方法)
    (2)动态方法解析
    (3)消息转发
    这三大阶段的具体流程可以根据流程图解释。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容