Runtime的本质(三)----objc_msgSend

OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为三大阶段:

  1. 消息发送
  2. 动态方法解析
  3. 消息转发

1. 消息发送

问:当空对象调用方法的时候,是怎么操作的?

当调用方法的时候,执行的是objc_msgSend函数objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>)
第一个参数是消息接收者,第二个参数是方法名。
我们在源码中找到objc_msgSend的具体实现,其是以汇编实现的。通过分析源码,可知,当消息接收者为空时,是执行类似以下这种操作:

void objc_msgSend(id receiver, SEL selector)
{
    if(receiver == nil) return;
    //1. 判断receiver是否为nil
	
	//2. 查找缓存
    
    //3. 执行后面的代码
}

也就是,当空对象调用方法的时候,直接return返回。


消息发送阶段的流程大致为:
在这里插入图片描述

2. 动态方法解析

当对象调用一个找不到的方法时,会进入动态方法解析阶段。

动态方法解析阶段,首先会判断之前是否有过动态方法解析:
如果之前已经进行了动态方法解析triedResolver = YES,则不执行动态方法解析;
如果之前没有进行动态方法解析triedResolver = NO,则执行动态方法解析;
在这里插入图片描述

动态方法解析阶段会根据对象是否为 元类还是类,区分调用不同的方法:
非元类对象(对象方法)调用:+(BOOL)resolveInstanceMethod:(SEL)sel
元类对象(类方法)调用:+(BOOL)resolveClassMethod:(SEL)sel

在这里插入图片描述

举一个栗子:

YZPerson.h
#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
- (void)run;
@end

YZPerson.m
#import "YZPerson.h"
@interface YZPerson()
@end
@implementation YZPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"%s", __func__);
    return [super resolveInstanceMethod:sel];
}
@end

YZPerson *person = [[YZPerson alloc] init];
[person run];

在这里插入图片描述说明,在找不到run方法的时候,会调用resolveInstanceMethod方法。

上述例子,仅仅说明或者证明:在找不到run方法的时候,会调用resolveInstanceMethod方法。

可以使用动态添加方法:
在这里插入图片描述
动态添加方法后,继续执行后面的goto retry,即继续执行消息发送
由于已经动态添加方法,将方法添加到了当前类中的方法列表里面,这次就找的到method,然后正常执行了。

动态方法解析流程:
在这里插入图片描述

动态方法解析最主要的作用与方式,就是通过动态添加一个方法,消息可以继续发送执行

3. 消息转发

当经过消息发送、动态方法解析过程后,仍没有找到方法名时,会进入第三个阶段:消息转发。即将自己处理不了的方法,转发给其他对象

在这里插入图片描述

主要是调用__forwarding__方法,里面会调用下面两个方法:
主要用到的方法是:
- (id)forwardingTargetForSelector:(SEL)aSelector
+ (id)forwardingTargetForSelector:(SEL)aSelector
其中,返回值是转发给其他对象。参数是处理不了的方法名。

举个例子:

YZCat.m文件

#import "YZCat.h"

@implementation YZCat
- (void)run
{
    NSLog(@"%s", __func__);
}
@end


YZPerson.m文件
#import "YZPerson.h"
#import <objc/message.h>
#import "YZCat.h"
@interface YZPerson()
@end
@implementation YZPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(run)) {
        return [[YZCat alloc] init];//返回可以处理的对象
		//上句代码相当于执行了
		//return objc_msgSend([[YZCat alloc] init], aSelector);
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

运行结果:
2020-05-23 12:16:49.554329+0800 runtime学习[8510:5105879] -[YZCat run]

可以看到,当前两个阶段都没法处理的时候,第三个阶段依消息转发然可以处理。

代码return [[YZCat alloc] init];当于执行了return objc_msgSend([[YZCat alloc] init], aSelector);

如果
- (id)forwardingTargetForSelector:(SEL)aSelector或者
+ (id)forwardingTargetForSelector:(SEL)aSelector
两个方法也没有执行或者返回nil,系统会调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,返回一个方法签名。

方法签名的目的是:得到返回值类型、参数类型

如果没有实现签名方法,则会报方法找不到的错误

在这里插入图片描述如果实现了方法签名,还需要调用- (void)forwardInvocation:(NSInvocation *)anInvocation函数。

其目的是:将方法签名,封装到Invocation中去

具体实现如下:

在这里插入图片描述

此处,如果仅仅给一个其他对象调用,完全可以在forwardingTargetForSelector:方法里面执行,而不需要走到这执行。
其实,forwardInvocation还可以做好多事情:
完全可以在[person test](test方法不存在)的情况下,在forwardInvocation里面只写一行打印,到时候执行test方法,会输出一行打印结果

在forwardInvocation里面,可以随意做事情,你即使什么都不写,只要方法签名那步写好了,也不会崩溃

消息转发流程:
在这里插入图片描述除了对象方法可以做消息转发,类方法也可以做消息转发。

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(eat)) {
        return [YZCat class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

结果:
2020-05-24 11:02:53.707519+0800 runtime学习[11653:5343993] +[YZCat eat]

需要注意的是:在消息转发的时候,即使原来调用的方法是个类方法,也可以转发给一个对象方法。

[YZPerson eat];//类方法调用

YZPerson.m文件
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(eat)) {
        return [[YZCat alloc] init];//转发给对象方法
    }
    return [super forwardingTargetForSelector:aSelector];
}

YZCat.m文件
+ (void)eat
{
    NSLog(@"%s", __func__);
}

- (void)eat
{
    NSLog(@"%s", __func__);
}

结果:
2020-05-24 11:11:16.169383+0800 runtime学习[11852:5352885] -[YZCat eat]

动态方法解析
1、调用resolveInstanceMethod:方法。允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回YES,重新开始objc_msgSend流程。这次对象会响应这个选择器,一般是因为它已经调用过了class_addMethod。如果仍没有实现,继续下面的动作。

消息转发
2、调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil对象。否则返回nil,继续下面的动作。注意这里不要返回self,否则会形成死循环。

3、调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil;传给一个NSInvocation并传给forwardInvocation:。

4、调用forwardInvocation:方法,将第三步获取到的方法签名包装成Invocation传入,如何处理就在这里面了,并返回非nil。

5、调用doesNotRecognizeSelector:,默认的实现是抛出异常。如果第三步没能获得一个方法签名,执行该步骤 。


这句代码,系统帮我们做了什么事?

@property (assign, nonatomic) int age;

这句代码,为我们做了:set/get方法声明、set/get方法实现、生成成员变量

  1. set/get方法声明
- (void)setAge:(int)age;
- (int)age;
  1. set/get方法实现
- (void)setAge:(int)age
{
    _age = age;
}

- (int)age
{
    return _age;
}
  1. 生成成员变量
    {
    _age;
    }

当我们使用了

@property (assign, nonatomic) int age;//.h
@dynamic age;//.m

YZPerson *person = [[YZPerson alloc] init];
person.age = 10;
NSLog(@"%d", person.age);

最后报reason: '-[YZPerson setAge:]: unrecognized selector sent to instance 0x100745230'错误。
这是因为,使用@dynamic age;就不会自动生成age的setter/getter方法的实现,也不会自动生成成员变量(ivar)。
需要注意的是,age的setter/getter方法的声明是不受影响的。

所以,我们可以使用@dynamic age;来达到setter/getter方法的实现在运行过程中修改,例如:

.h文件
@property (assign, nonatomic) int age;

.m文件
@dynamic age;

void setAge(id self, SEL _cmd, int age)
{
    NSLog(@"age is %d", age);
}

int age(id self, SEL _cmd)
{
    return 100;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"%s", __func__);
    if (sel == @selector(setAge:)) {
        class_addMethod(self, sel, (IMP)setAge,
                        "v@:i");
        return YES;
    }else if (sel == @selector(age)) {
        class_addMethod(self, sel, (IMP)age,
                        "i@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

调用
YZPerson *person = [[YZPerson alloc] init];
person.age = 10;
NSLog(@"%d", person.age);

运行结果:
2020-05-24 12:45:36.960569+0800 runtime学习[12661:5405426] +[YZPerson resolveInstanceMethod:]
2020-05-24 12:45:36.961027+0800 runtime学习[12661:5405426] age is 10
2020-05-24 12:45:36.961090+0800 runtime学习[12661:5405426] +[YZPerson resolveInstanceMethod:]
2020-05-24 12:45:36.961163+0800 runtime学习[12661:5405426] 100
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值