美文网首页
Runtime之objc_msgSend执行流程

Runtime之objc_msgSend执行流程

作者: ychen3022 | 来源:发表于2022-12-28 09:07 被阅读0次
    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)消息转发
      这三大阶段的具体流程可以根据流程图解释。

    相关文章

      网友评论

          本文标题:Runtime之objc_msgSend执行流程

          本文链接:https://www.haomeiwen.com/subject/jgxmjqtx.html