方法的本质?SEL是什么?IMP是什么?两者之间的关系又是什么?
带着这样的问题我们先来看一段代码
self&super
@implementation MMPerson
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
调用:
MMPerson *person = [MMPerson alloc];
person = [person init];
输出:
MMPerson.m:(17): MMPerson
MMPerson.m:(18): MMPerson
为什么我们的[self class]和[super class]的输出结果都是当前类呢?
带着这样的疑问我们将上面的代码通过clang转化为C++:
static instancetype _I_MMPerson_init(MMPerson * self, SEL _cmd) {
self = ((MMPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MMPerson"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dq_m1fkwntx6rsbzm4157sqxsnc0000gn_T_MMPerson_8ad7e9_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dq_m1fkwntx6rsbzm4157sqxsnc0000gn_T_MMPerson_8ad7e9_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MMPerson"))}, sel_registerName("class"))));
}
return self;
}
为了方便阅读,我们去掉一些冗余的代码
NSStringFromClass(objc_msgSend((id)self, sel_registerName("class")));
NSStringFromClass(objc_msgSendSuper({(id)self, (id)class_getSuperclass(objc_getClass("MMPerson"))}, sel_registerName("class"))));
我们发现
super关键字底层调用的是objc_msgSendSuper。
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
struct objc_super {
id receiver;
Class cls; // the class to search
}
我们发现
objc_super结构体的第一个成员变量receiver也就是我们的消息接受者。对比上面转换后的代码我们可以发现receiver的值传入的是self,也就是说我们当前的消息接受者就是我们当前的对象,只不过它是从父类的消息列表中开始查找函数的实现。最后交由self去处理。 所以上面的两次输出都是MMPerson。
消息传递流程
我们接着在MMPerson代码中添加一个方法名,在外部掉用。
@interface MMPerson : NSObject
- (void)sendMessage;
@end
调用
MMPerson *person = [MMPerson alloc];
person = [person init];
[person sendMessage];
结果:抛出异常
-[MMPerson sendMessage]: unrecognized selector sent to instance 0x600001d24690
它会经历这样几个过程
- 1.快速查找:消息发送给对象时,消息传递函数遵循对象的
isa指针指向类结构的指针,在该结构中它查询结构体变量(objc_msgSend)~cache_t缓存消息和methodLists中的方法SEL(方法选择器)。 - 2.慢速查找:递归自己和父类:
lookUpImpOrForward,superclass的指针,去父类的cache和methodLists查找方法SEL。 - 3.一旦找到
SEL,该函数就会调用methodLists的方法并将接收对象的指针传给它。 - 4.如连续失败直到
NSObject类,它的superclass也就是它自己本身。 - 5.查找不到消息:动态方法解析:
resolveInstanceMethod。 - 6.消息快速转发:
forwardingTargetForSelector。 - 7.慢速消息转发:
methodSignatureForSelector&forwardInvocation。 - 8.抛出异常:
doesNotRecognizeSelector。
SEL是方法编号,IMP是我们的函数实现指针。找IMP就是找函数的过程。SEL就相当于是我们书本目录的标题。IMP就相当于是我们书本目录标题对应的页码。
查找SEL过程中isa的走向:

Runtime的发送消息隐藏的参数
- (void)sendMessage{
NSLog(@"%@",NSStringFromSelector(_cmd));
}
输出:
MMPerson.m:(23): sendMessage
每次当我们向一个对象发送消息时,也就是Objective-C调用方法的时候,传递的所有参数,还包括两个隐藏的参数:
1.)接收者对象
2.)调用的方法SEL _cmd
这两个参数没有在定义中声明,而是在编译代码时插入方法实现的。
消息发送的几种方法:
MMPerson有这样的方法
- (void)sendInvocationMessage:(NSString *)message{
NSLog(@"%@%@",NSStringFromSelector(_cmd),message);
}
-
1)直接调用
[person sendInvocationMessage:@"hello"];
-
2)performSelector
[person performSelector:@selector(sendInvocationMessage:) withObject:@"hello"];
-
3)Invocation
//方法签名
NSMethodSignature *methodSign = [self methodSignatureForSelector:@selector(sendInvocationMessage:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSign];
[invocation setTarget:person];
[invocation setSelector:@selector(sendInvocationMessage:)];
NSString *arg1 = @"testArg1";
//index为2 是因为0、1两个参数已经被self和_cmd占用,其实可以这样设置:
[invocation setArgument:&arg1 atIndex:2];
//调用
[invocation invoke];
总结:
回答一下最开始提出的问题:
- 方法的本质是
消息传递 SEL是方法编号IMP是指向函数实现指针
1)消息机制就是向接收者发送消息,并带有参数,根据接收者对象的数据结构,找到相关发放实现,最后达到这个消息的目的 2)
objc_msgSend是Runtime的核心,Objective-C中调用对象方法就是消息传递 3)objc_msgSend并不是直接调用方法实现(IMP)而是发送消息,让类的结构体去动态查到方法实现,所以在为查找到方法实现之前我们可以动态的去修改这个方法的实现