OC消息机制(objc_msgSend执行流程)

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大阶段
消息发送(当前类,父类中查找), 动态方法解析,消息转发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值