序言
读完这篇文章后,我们就可以弄明白“对象、类是怎么调用方法的”
一、消息发送机制
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 | // 返回该对象所属的类 |
举个例子:
1 | @implementation INEStudent : INEPerson |
[self class]
和[self superclass]
就不用说了,消息接收者都是self
,会从Student
类的方法缓存和方法列表里开始查找class
和superclass
方法,而这两个方法都是NSObject
类的方法,所以会一层一层往上,找到后发现两者的实现就是返回当前消息接收者self
的类和父类。
[super class]
和[super superclass]
的消息接收者其实都还是self
,只不过会跳过Student
类,直接从Person
类的方法缓存和方法列表里开始查找class
和superclass
方法,最后也还是找到NSObject
类那里,找到后发现两者的实现就是返回当前消息接收者self
的类和父类,而self
又没变。