个人技术生活分享

文明其精神,野蛮其体魄

0%

【Runtime】消息发送机制,动态方法解析,消息转发机制

img

序言

读完这篇文章后,我们就可以弄明白“对象、类是怎么调用方法的”

一、消息发送机制objc_msgSend

二、动态方法解析resolveMethod

三、消息转发机制objc_msgForward

四、程序崩溃unrecognized selector sent to instance

五、super关键字objc_msgSendSuper

一、消息发送机制objc_msgSend

消息发送机制是指,OC对象调用[object methondName],都会转换为objc_msgSend(object,@selector(methonName))函数的调用,也就是说给一个对象发送消息,这个转换过程是在编译时完成的,而具体怎么给对象发消息则是在运行时完成的,那objc_msgSend函数内部具体是怎么给对象发消息的呢?(消息发送流程、方法调用流程)

1、进入objc_msgSend后系统会首先判断接收者是不是nil,如果是nil直接retain,结束该方法的调用,程序不会崩溃;

2、如果不是nil,则根据对象的isa指针找到该对象所属的类,去这个类的方法缓存(cache)里查找方法(方法缓存是通过散列表实现的,查找效率很高),找到了就直接调用,如果没找到,则去类的方法列表(methods)里查找,这里对于已经拍过序的方法采用二分查找,对于为排序的方法列表则采用遍历查找,如果在类的方法列表中找到了方法,则首先把该方法缓存到cache中,然乎调用该方法如果没有找到,则会根据当前类的superclass指针找到它的父类,曲父类里查找;

3、找到父类后,会首先去父类的方法缓存(cache)里查找方法,如果找到了,则首先把该方法缓存到当前类的cache中(注意不是父类的cache),然后调用该方法,若果没有找到,则去父类的方法列表(methods)中查找。如果在父类的方法列表里找到了方法,则首先把该方法缓存到当前类的缓存(cache)里,然后调用该方法,如果没有找到,则会一层一层往上,直到根类,直到nil

4、如果到了nil,还没找到方法,就会触发动态解析了。

补充一下:

消息发送关注的仅仅是消息接收者和SEL,无非就是通过消息接收者的isa指针和superclass指针去找SEL嘛,根本就没有什么绝对的标识来表明某个方法是实例方法还是类方法,所以如果出现类调用实例方法也不要惊讶哦,比如[NSObject -test]是没有问题的,你只要抓紧方法调用流程这条线就可以了。

二、动态方法解析resolveMethod

如果正常的消息发送流程走完,没有找到方法,就会触发动态方法解析,动态方法解析是指,如果我们在编译时没有为某个方法提供实现,可以在运行时通过类的+resolveInstanceMethod:方法或者+resolveClassMethod:方法动态地为这个方法添加实现。

1.触发动态解析后,如果系统发现没有找到实例方法,就会调用该类的+resolveInstanceMethod:方法,我们可以在这个方法里动态地为没有找到的方法添加实现,会添加到methods里;如果发现没有找到类方法,就会调用该类的+resolveClassMethod方法,我们也可以在这个方法里动态地为没找到的方法添加实现,会添加到methods里;

2.动态解析完成后,会重新走一次消息发送流程来查找方法;

3.如果动态方法解析也没有解决问题,就会触发消息转发机制。

三、消息转发机制objc_msgForward

如果正常的消息发送流程和动态方法解析都走完了,还是没找到方法,就会触发消息转发机制。消息转发机制是指,把消息转发给别的对象,让别的对象来调用这个方法。能走到这一步,已经表明这个类本身无法调用这个方法,交给其他类来处理吧。消息转发又分为直接消息转发和完整消息转发。(消息转发源码不开源)

1、直接消息转发:

直接消息转发是指,系统会调用该类的forwardingTargetForSelector:方法,我们可以在这个方法里直接把消息转发给别的对象。

2.完整消息转发

完整消息转发是指,系统会调用该类的methodSignatureForSelector:方法和forwardInvocation:方法,我们可以在这两个方法里把消息转发给别的对象。完整消息转发相比直接消息转发可以做更多复杂操作,甚至你可以不做消息转发,直接在forwardInvocation做我们想要做的事。

四、程序崩溃unrecognized selector sent to instance

如果消息转发机制都走完了,还是没处理这个方法的调用,那就彻底没救了,程序crash,报错提示unrecognized selector sent to instance也就是说,这个错误是消息转发机制报的,而不是消息发送机制或者动态解析时报的。所以如果别人问你“什么时候会报unrecognized selector sent to instance的错误”,你最好把整个消息发送流程、动态方法解析、消息转发流程都给他说一遍,而不仅仅是说“找不到方法的实现时”——这只是消息发送阶段。

五、super关键字objc_msgSendSuper

我们知道self代表的是当前对象,但super代表的可不是父类的对象。super关键字仅仅是一个编译器指示符,它的作用就是告诉当前消息的接受者直接去它父类里查找方法,而不是从当前类开始查找,消息的接收者还是self

super调用[super methodName],都会转换为objc_msgSendSuper({self,[self class]},@seletor(methodName))函数调用,可见本质上确实还是给self发送消息,只不过直接去[self class]里查找方法而已。

下面是Runtime的源码(NSObject.mm文件):

1
2
3
4
5
6
7
8
9
// 返回该对象所属的类
- (Class)class {
return object_getClass(self);
}

// 返回该对象所属类的父类
- (Class)superclass {
return [self class]->superclass;
}

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation INEStudent : INEPerson

- (instancetype)init {

self = [super init];
if (self) {

NSLog(@"%@", [self class]); // INEStudent
NSLog(@"%@", [self superclass]); // INEPerson

NSLog(@"%@", [super class]); // INEStudent
NSLog(@"%@", [super superclass]); // INEPerson
}

return self;
}

@end

[self class][self superclass]就不用说了,消息接收者都是self,会从Student类的方法缓存和方法列表里开始查找classsuperclass方法,而这两个方法都是NSObject类的方法,所以会一层一层往上,找到后发现两者的实现就是返回当前消息接收者self的类和父类。

[super class][super superclass]的消息接收者其实都还是self,只不过会跳过Student类,直接从Person类的方法缓存和方法列表里开始查找classsuperclass方法,最后也还是找到NSObject类那里,找到后发现两者的实现就是返回当前消息接收者self的类和父类,而self又没变。