Runtime的本质(三)----objc_msgSend
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为三大阶段:
消息发送
动态方法解析
消息转发
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
@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
#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中去
具体实现如下:
在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方法实现、生成成员变量
- set/get方法声明
- (void)setAge:(int)age;
- (int)age;
- set/get方法实现
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
- 生成成员变量
{
_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