objc_msgSend用法
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
Person *person = [[Person alloc] init];
[person personTest];
// objc_msgSend(person, @selector(personTest));
// 消息接收者(receiver):person
// 消息名称:personTest
[Person initialize];
// objc_msgSend([MJPerson class], @selector(initialize));
// 消息接收者(receiver):[MJPerson class]
// 消息名称:initialize
- 当用对象调用方法时,如以下:
[person test]
时,其实是被转化成了C语言的调用objc_msgSend(person,sel_registerName("test"))
,其中sel_registerName("test") == @selector(test)
,如果是调用类方法则为:objc_msgSend([Person class],@selector(initialize))
,这就是OC的方法调用,也叫消息机制,给方法调用者发送消息。 - OC中方法调用,其实都是转换为objc_msgSend函数的调用
- objc_msgSend的执行流程可以分为3大阶段:
- 消息发送
- 动态方法解析
- 消息转发
objc_msgSend流程解析
![](https://img.haomeiwen.com/i1419593/1c456af45f7f2e56.png)
- 在消息发送阶段会去尝试找到这个方法来进行调用,如果在当前类的方法缓存中查找,如果找不到则去它的
rw_t
缓存中查找,如果当前类没找到,则去父类中找,如果找到则直接调用,就不会进行动态方法解析,否则会进行动态方法解析(这两个方法中的其中一个:resolveInstanceMethod
和resolveClassMethod)
,允许开发者动态去创建一个新的方法,如果动态方法解析没有处理,则进入消息转发阶段,有可能会转发给另外一个对象去调用,最终完成方法调用,如果最终这三个步骤都没有找到则会报一个:unrecognized selector sent to instance
。
objc_msgSend执行流程01-消息发送:
![](https://img.haomeiwen.com/i1419593/a66d48ed6f320f41.jpeg)
如果在自己的方法缓存和父类的方法缓存里没有找到方法,则就会进入动态方法解析
2. 动态方法解析
- 如果不是元类对象则调用:
resolveInstanceMethod
,如果是元类对象则调用:resolveClassMethod
,如果不实现这两个方法表示动态方法解析不做任何处理
动态解析流程图.png
完整的代码实现如下:
@implementation Person
struct method_t
{
SEL sel;
char *types;
IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test))
{
//第一种做法
//获取其他方法
// struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
// //动态添加test方法的实现
// class_addMethod(self, sel, method -> imp, method->types);
//第二种做法
Method otherMethod = class_getInstanceMethod(self, @selector(other));
//动态添加方法的实现,IMP表示方法的实现
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)other
{
NSLog(@"%s",__func__);
}
上面的代码表示如果没有找到 test
方法则就会调用 other
方法
3. 消息转发
- 将消息转发给别人,如果方法没有在自己类和父类里找到,同时也没有做动态消息转发的话,就会来到消息转发。
-
forwadingTargetForSelector:(SEL)aSelector
将aSelector
转发给有能力处理的类,相当于新的对象调用这个aSelector
方法。
![](https://img.haomeiwen.com/i1419593/651c1b5716bb61b2.png)
代码如下:
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test))
{
return [[Cat alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
上面的代码Person类不处理test
方法而将其转发给Cat
类处理,Cat
类里的代码如下:
@implementation Cat
- (void)test
{
NSLog(@"%s",__func__);
}
@end
此时当Person
对象调用test
方法时,最终会调用Cat
类的test
方法。
- 如果
forwardingTargetForSelector
返回的是nil,则会进入到methodSignatureForSelector
方法,如果methodSignatureForSelector
返回了一个合理的签名则会调用另外一个方法forwardInvocation
,如果方法签名为空则不调用,NSInvocation
封装了一个方法调用,包括:方法调用者、方法、方法参数-
invocation.selector
方法名 -
invocation.target
方法调用者 [invocation getArgument:NULL atIndex:0]
-
//如果是类方法则需要将下面的方法变为+号
//methodSignatureForSelector也需要改为+号
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test))
{
return nil;
//return [[Cat alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test))
{
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法、方法参数,其中Invocation.target为方法的调用者,Invocation.selector为方法名,Invocation.getArgument为获取方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[Cat alloc]init];
// [anInvocation invoke];
//或
[anInvocation invokeWithTarget:[[Cat alloc]init] ];
}
完整的动态解析和消息转发流程如下图所示:
![](https://img.haomeiwen.com/i1419593/93d20453c08d75cb.png)
dynamic用法
@dynamic age;
上面的这句代码是提醒编译器不要自动生成 get
和 set
方法,也不要自动生成成员变量
面试题
-
讲一下OC的消息机制
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
-
objc_msgSend底层有3大阶段
- 消息发送(当前类、父类中查找)、动态方法解析、消息转发
网友评论