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 


//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执行流程)