Objective-c runtime之消息转发机制

我们使用某个实例对象调用该对象所属类的某个方法:
[receiver message];
以前:receiver调用了message方法。
OC:message这个方法向receiver发送了消息。

OC方法调用会转化为C语言方法调用:
转化为:objc_msgSend(id receiver , SEL selector , …);
1、会根据receiver和selector一起来确定方法的实现体,即IMP
2、会传递相关的信息给IMP
id (*IMP)(id receiver, SEL, _cmd , … ); (IMP)是一个函数指针
3、IMP自己去执行,并且将返回值作为最终的返回值。

在类中和超类中寻找

举个例子:
Person *p = [[Person alloc] init ];
[p run] ;

1、 根据p这个对象,先拿到自己的isa指针,如果找到了。
1.1 如果run这个方法,是实例方法,就去Person类中去寻找对应的方法执行。
1.2 如果run这个方法,是类方法,就去Person的元类中去寻找对应的方法执行。
2、如果在类和元类中都没有找到相应的方法,就去superClass中寻找。

上述不管是isa还是superClass,如果找到了对应的方法,就去执行对应的IMP。

3、如果1,2都失败,则进入消息转发机制。

接下来再举个例子:
定义两个类,Father类和Child类,Child继承Father类,并且重写Child类的init方法。

@interface Child : Father

@end

#import "Child.h"

@implementation Child
-(instancetype) init{  //重写init方法
    if(self = [super init]){
        NSLog(@"self class : %@", self.class);
        NSLog(@"super class : %@", super.class);
    }
    return self;
}
@end

主函数中实例化一个Child对象。

#import <Foundation/Foundation.h>
#import "Child.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Child *son = [[Child alloc] init ];
    }
    return 0;
}

运行,得到结果:

2021-03-18 16:30:20.926985+0800 Project08[84419:4845071] self class : Child
2021-03-18 16:30:20.927322+0800 Project08[84419:4845071] super class : Child
Program ended with exit code: 0

这里就体现了OC的一个陷阱,可能大家会觉得super.class的运行结果应该是Father,我第一次也是这么认为的,后来分析之后,才明白其中的原因。

首先,self.class
会转变为:[self class];
然后被转变为C语言函数:

objc_msgSend(self,class);

self 是一个实例
self里面的isa指针是指向Child这个类的。
1、Child类中没有class这个方法。
2、从Child类的superClass中寻找,Father类中也没有class这个方法。
3、从Father的superClass中寻找,在NSObject中找到了。
从而执行该方法。

这里我附上runtime源码中关于class方法的定义:

- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");  //这是实例方法
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");  //这是类方法

而对于super.class方法
会转换成:

objc_msgSendSuper(objc_super *super, SEL selector,...);

其中,objc_super是一个结构体。
如下:

struct objc_super{
id receiver,
Class superClass
};

最终,该调用会转化为:

objc_msgSendSuper(objc_super->receiver, SEL selector,...);

而objc_super->receiver 等价于 self

所以,最后,super.class执行结果和self.class执行结果一样。

执行消息转发

上面已经讲过,如果在self中和superClass中都没有找到方法,就会进入消息转发机制。
消息转发机制主要分为以下三步。
如图:
在这里插入图片描述
1、当我们调用不存在的实例方法,OC会启用消息转发机制。
+(bool) resolveInstanceMethod:(SEL)sel;
类方法没有找到,会调用这个方法去执行:
+(bool) resolveClassMethod:(SEL)sel;
在现在的接收者上进行一次拦截。(class_addMethod)(先让类接受者自己处理)

2、 第一步的两个方法返回NO,就会调用: (交给其他接受者)
-(id) forwardingTargetForSelector:(SEL)sel;

3、第二步的方法返回nil,会触发两个方法调用:
-(NSMethodSignature*) methodSignatureForSelector:(SEL)sel; (拿到方法签名)如果返回值为nil,则消息无法处理
-(void) forwardInvocation: (NSInvocation) anInvocation;

接下来举个例子
首先定义一个Cat类,并定义一个方法jump,但是不去实现它。

进入第一步转发

@interface Cat : NSObject
-(void) jump;   //只定义,不实现,出触发消息转发机制
@end

然后再主函数中实例化一个Cat对象,并执行jump方法。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Cat * cat = [[Cat alloc] init ];
        [cat jump];    //由于jump方法只定义没有实现,所以会触发OC的消息转发机制。
    
    }
    return 0;
}

程序会崩溃,显示没有找到selector的实例
在这里插入图片描述
接下来我们实现第一种转发机制,使用resolveInstanceMethod方法实现转发,先让方法的receiver自己来处理这种消息转发。

#import "Cat.h"
#import <objc/runtime.h>
@implementation Cat

//第一步转发,自己实现resolveInstanceMethod方法来拦截转发
//自己动态的添加方法去拦截
+(BOOL) resolveInstanceMethod: (SEL) sel{
    if([NSStringFromSelector(sel) isEqualToString:@"jump"]){
        class_addMethod(self, sel, (IMP)jump, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}
//IMP: id(*IMP)(id sel, SEL _cmd)
void jump(id self, SEL _cmd){
    NSLog(@"cat is jumping!");
}

@end

我们打一个断点,观察执行过程,可以看到,程序确实进入了消息转发。
在这里插入图片描述
执行结果也和料想的一样。

2021-03-20 00:04:48.796121+0800 Project09[86710:5090969] cat is jumping!
Program ended with exit code: 0

进入第二步转发

接下来演示进入第二步消息转发
定义一个Dog类,同样只定义jump方法,不实现。

@interface Dog : NSObject
-(void) jump;
@end

如果直接执行,一样会崩溃。这里我们让resolveInstanceMethod返回NO,这样就会让消息转发机制进入第二步,在第二步中,会调用forwardingTargetForSelector方法,字面意思就是让selector转向别的接收者。同样,我们首先判断sel是否是jump方法,如果是,就转让给cat,让cat替我们执行该方法。

#import "Dog.h"
#import "Cat.h"
#import <objc/runtime.h>
@implementation Dog

//如果第一种转发机制失败,那么进入第二种转发机制
+(BOOL) resolveInstanceMethod: (SEL) sel{
    return NO;   //第一步转发返回false
}
//第一步转发失败,进入第二步转发
//交给其他接受者去处理
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if([NSStringFromSelector(aSelector) isEqualToString:@"jump"]){
        return [[Cat alloc] init ];   //返回转发给其他接收者执行的对象
    }
    //如果不是jump,就正常执行
    return [super forwardingTargetForSelector:aSelector];
}

@end

打一个断点,观察确实进入了消息转发第二步。
在这里插入图片描述执行结果和cat的执行结果一样,这说明确实将sel转让给cat了。

2021-03-20 00:11:30.241038+0800 Project09[86733:5095032] cat is jumping!
Program ended with exit code: 0

进入第三步转发

接下来演示第三步转发。如果在第二步中,方法forwardingTargetForSelector返回的是nil,就会进入第三步转发。
这里我们定义一个Person类,同样只定义不实现jump方法。
我们让resolveInstanceMethod方法返回NO,进入第二步转发,然后让forwardingTargetForSelector返回nil,进入第三步转发。在第三步转发中,先调用methodSignatureForSelector放回方法的签名。然后再调用forwardInvocation方法,进行自定义处理。在该方法中,我们可以继续将方法委托给别的对象,也可以直接将该方法的selector改变成别的selector,从而执行别的方法。

//Person演示第一第二步都没有处理,进入第三步的转发

#import "Person.h"
#import <objc/runtime.h>
#import "Cat.h"
@implementation Person
//如果第一种转发机制失败,那么进入第二种转发机制
+(BOOL) resolveInstanceMethod: (SEL) sel{
    return NO;   //第一步转发不处理,返回NO
}
//第一步转发失败,进入第二步转发
-(id)forwardingTargetForSelector:(SEL)aSelector{
    return nil;   //第二部也不处理,返回nil
}

//进入第三步转发机制
//先返回该方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if([NSStringFromSelector(aSelector) isEqualToString:@"jump"]){
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];  //返回方法签名
    }
    //如果不是jump,就正常执行
    return [super methodSignatureForSelector:aSelector];
}

-(void) forwardInvocation:(NSInvocation *)anInvocation{
//    //1、调用其他接受者去处理
//    [anInvocation invokeWithTarget:[[Cat alloc] init ]];
    
    //2、更换selector
    anInvocation.selector = @selector(fly);
    [anInvocation invokeWithTarget:self];
    
}
-(void) fly{
    NSLog(@"I am flying!");
}
@end

上面我实现了更换jump的selector为fly方法,执行结果:

2021-03-20 00:17:40.149653+0800 Project09[86762:5099220] I am flying!
Program ended with exit code: 0

可见转发成功。需要提醒的是,在调用methodSignatureForSelector返回方法的签名时,如果返回的是nil,则会直接报错,显示消息无法处理。
以上就是oc中runtime的消息转发机制的详细解析。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值