先看一下流程图:
在 Object-C 中调用方法最终都会翻译成调用方法实现的的函数指针,并传递给它一个对象指针、一个选择器和一组函数参数。
objc_msgSend 的工作方式如下:
- 检查接受对象是否为
nil
。如果是,调用nil
处理程序。 - 在垃圾收集环境中(ios 不支持,写在这里是为了内容的完整性),检查有没有锻炼选择器(retain、release、autorelease、retainCount),如果有,返回 self。是的,这意味着在垃圾收集环境中 retainCount 会返回 self,不过你应该用不到。
- 检查类缓存中是不是已经有方法实现了,有的话,直接调用。
- 比较请求的选择器和类定义的选择器,如果找到了,调用方法实现。
- 比较请求的选择器和父类中定义的选择器,然后是父类的父类,以此类推。如果找到选择器,调用方法实现。
- 调用
resolveInstanceMethod:
( 或者resolveClassMethod:
)。如果它返回YES
,那么重新开始。这一次对象会相应这个选择器,一般是因为它已经调用过class_addMethod
。 - 调用
forwardingTargetForSelector:
,如果返回非 nil
,那就把消息发送到返回的对象上。这里不要返回 self,否则会形成死循环。 - 调用
methodSignatureForSelector:
,如果返回一个非 nil
,创建一个NSInvocation
并传给forwardInvocation
。 - 调用
doesNotRecognizeSelector:
,默认是抛出异常。
例子🌰
Xcode何时会报unrecognized selector
的错误
1 | - (void)viewDidLoad { |
当向AAPerson
发送test
这个消息时,runtime
会根据对象的isa指针
找到该对象实际所属的类,然后在该类的方法列表以及父类的方法列表里面找相应的方法运行,如果在最顶层的父类中依然找不到相应的方法实现时,程序在运行时就会报unrecognized selector sent to
的错误并且崩溃,但是在此之前,objc的运行时给出了三次避免程序崩溃的机会。
1、Method resolution
objc运行时会调用+resolveInstanceMethod:
或者+resolveClassMethod:
,让我们有机会提供一个函数实现而不导致程序崩溃,如果在这里面添加了函数,系统就会重新启动一次消息发送的过程,否则就会进入到消息的快速转发流程。
1 | + (BOOL)resolveInstanceMethod:(SEL)sel { |
2、Fast forwarding
如果目标对象
实现了-forwardingTargetForSelector:
的方法,runtime就会调用这个方法,给我们一个机会把这个消息转发给其他的对象,只要这个方法返回值不是nil和self,整个消息发送的过程就会被重启,这时发送的对象会变成我们返回的这个对象,否则就会移到下一步。
1 | - (id)forwardingTargetForSelector:(SEL)aSelector { |
3、Normal Fowarding
如果上面两种方法都没有被实现的话,就会来到第三步 —— 普通转发,这是runtime给我们最后一次避免崩溃的机会,首先它会-methodSignatureForSelector:
来获得函数的参数和返回值类型,如果返回值为nil
,则runtime会发出-doesNotRecognizeSelector:
的消息,程序崩溃。如果返回了一个函数签名,runtime会创建一个NSInvocation
对象并发送-forwardInvocation:
的消息给目标对象。
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { |
如果上面的三步都没有实现的话,就会调用-doesNotRecognizeSelector:
,程序崩溃。