文章目录
一、消息发送概述
1、消息发送机制
2、消息转发机制
二、消息是如何转发
1、Method resolution
2、Fast fowarding
3、Normal forwarding
一、消息概述
1、消息发送机制
在OC中,方法的调用不再理解为对象调用其方法,而是要理解成对象接收消息,消息的发送采用"动态绑定"机制,具体会调用哪个方法直到运行时才能确定,确定后才会去执行绑定的代码。
方法的调用实际上就是告诉对象要干什么,给对象传递一个消息,对象为接收者(receiver),调用的方法及参数就是消息(message),给一个对象传递消息表达为[receiver message];
接收者的类型可以通过动态类型识别在运行时确定。
在消息传递机制中,当开发者编写[receiver message];
语句发送消息后,编译器都会将其转化成对应的一条objc_msgSend C语言消息发送原语。具体格式为: void objc_msgSend(id self,SEL cmd,....)
这个原语函数参数可变,第一个参数填入消息的接收者,第二个参数就是消息"选择子",后面跟着可选的消息的参数。有了这些参数,objc_msgSend就可以通过接收者的isa指针,到其类对象的方法列表中以选择子的名称为"键"寻找对应的方法。若找到对应的方法,则转到其实现代码执行,否则继续从父类中寻找,如果到根类还是无法找到对应的方法,说明该接收者对象响应该消息,那么就会触发消息转发机制,给开发者最后一次挽救程序crash的机会。
2、消息转化机制
如果在消息传递过程中,接收者无法响应收到的信息,那么就会触发进入消息转发机制。
消息转发依次提供了3道防线,任何一个起作用都可以挽救此次消息转发。
按照先后顺序3道防线依次为
(1)动态补加方法实现
+(BOOL)resolveInstanceMethod:(SEL)sel
+(BOOL) resolveClassMethod:(SEL)sel
(2)直接返回消息转发到的对象(将消息发送给另一对象去处理)
- (id)forwardingTargetForSelector:(SEL)aSelector
+ (id)forwardingTargetForSelector:(SEL)aSelector
(3)手动生成方法签名并转发给另一对象
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
+ (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
+ (void)forwardInvocation:(NSInvocation *)anInvocation
二、消息是如何转发
unrecognized selector
错误主要发生在消息接收者无法正确响应发来的消息时,即无法找到消息对应的实现时。
unrecognized selector
错误指无法识别selector
.即receiver无法处理发出的的消息。根据消息传递机制和动态绑定机制可知,想接收者对象发送消息后,会根据其isa指针到其类对象的方法列表中以方法名为键找对应方法的实现,如果找不到,那么启动消息转发机制,但如果消息转发机制仍然无法弥补,那么久意味着接收者无法响应及正确处理该消息,就会报unrecognized selector
错误导致程序crash。
消息在运行时系统的翻译模式如下 :
引入 : #import <objc/message.h>
[object sendMessage]
// 在编译时,会被解释成
objc_msgSend(object, @selector(sendMessage));
备注 : 如果上述 代码 在你工程编译不通过的话,可在 Build Settings 将 Enable Strict Checking of objc_msgSend Calls 将其值设置成 NO 即可。
可理解成 向 对象 object 发送 sendMessage 消息。当向object 发送消息时,runtime库会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。
object : 当前消息的接受者
sendMessage : 发送消息内容
但是,在这之前,runtime 会给出 三次 的拯救 机会。查看了一些资料,这三次机会可大概描述成 如下:
1、Method resolution
2、Fast fowarding
3、Normal forwarding
下面是一个具体的例子
场景 : 定义Person类继承NSObject
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
//Person.m
#import "Person.h"
@implementation Person
@end
// main.m文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Person.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *jack = [Person new];
[jack performSelector:@selector(speakIMP:) withObject:@"jack"];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
运行报错如下:
-[Person speakIMP]: unrecognized selector sent to instance 0x60400001b260
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person speakIMP]: unrecognized selector sent to instance 0x60400001b260'
说明 : Person 及其父类皆未找到 speakIMP 方法未实现
1、Method resolution
objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发。
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(speakIMP:))
{
// 方式一: 调用下面C 函数
//class_addMethod([self class], sel, (IMP)speakIMP, "b@:@");
//方式二 :调用下面OC 函数 class_getMethodImplementation 改变现有的实现
class_addMethod([self class], sel, class_getMethodImplementation(self,@selector(dynamicAddMethod:)), "b@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}// 实例方法
+ (BOOL) resolveClassMethod:(SEL)sel
{
return [super resolveClassMethod:sel];
}// 类方法
// 实现C 函数
BOOL speakIMP(id self,SEL _cmd,NSString*name){
NSLog(@"方法添加成功,且接受参数为:%@",name);
return YES;
}
// oc 函数
- (BOOL)dynamicAddMethod:(NSString *name)
{
if (name != nil)
{
NSLog(@"方法添加成功,且接受参数为:%@",name);
return YES;
}
return NO;
}
关于 class_addMethod 参数的说明
1、第一个参数 : Class _Nullable cls。 在哪个类添加方法
2、第二个参数 : SEL _Nonnull name。添加的方法的编号
3、第三个参数 : IMP _Nonnull imp 。方法的实现(其实就是函数指针)。这个C函数,至少包含两个参数 id self 和 SEL _cmd
4、第四个参数 : const char * _Nullable types 。v代表 void , : 代表 SEL
例如 :
”v@:”意思就是这已是一个void类型的方法,没有参数传入。
“i@:”就是说这是一个int类型的方法,没有参数传入。
”i@😡”就是说这是一个int类型的方法,又一个参数传入。
注意 : performSelector是运行时系统负责去找方法的。
假设Person 没有实现 resolveClassMethod 方法,或者 返回NO,那么就会进行消息转发(Fast fowarding)。
.
.
.
2、Fast fowarding(让别的对象去执行这个函数)
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 **只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。**否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
OtherObject *jack = [[OtherObject alloc] init];
if ([jack respondsToSelector:aSelector])
{
// 相当于[jack speakIMP:@"jack"];
return jack;
}
return [super forwardingTargetForSelector:aSelector];
} // 只要不返回 nil 和 self 消息发送过程就会被重启
.
.
.
3、Normal forwarding
这一步是Runtime最后一次给你挽救的机会。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。
(1)如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。
(2)如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
//查找父类签名
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if(methodSignature == nil)
{
methodSignature = [NSMethodSignature signatureWithObjCTypes:"@:@"];
}
return methodSignature;
}//如果签名 不为nil ,那么runtime 会创建一个 NSInvocation 对象,并发送forwardInvocation:消息
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = anInvocation.selector;
OtherObject *jack = [[OtherObject alloc] init];
if ([jack respondsToSelector:sel])
{
[anInvocation invokeWithTarget:jack];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"程序crash了");
}//当方法签名为nil,调用此方法。程序crash。
runtime 消息发送的三次补救机会,可总结入下图所示