1、objc_msgSend本质
在OC中,方法调用其实就是转换成objc_msgSend函数的调用。
发送message只需要指定 对象 和 SEL ,Runtime的objc_msgSend会根据信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用。
MJPerson *peron = [[MJPerson alloc] init];
[peron personTest];
//编译后.cpp文件:
//((void (*)(id, SEL))(void *)objc_msgSend)((id)peron, sel_registerName("personTest"));
//消息接受者(receiver): peron
//消息名称: “personTest”
[MJPerson initialize];
//编译后.cpp文件:
// ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("initialize"));
//消息接受者(receiver): objc_getClass("MJPerson"), 是MJPerson这个类,而不是实例对象
//消息名称: initialize
NSLog(@"%p",sel_registerName("personTest"));
NSLog(@"%p",@selector(personTest));
//以上两句打印的地址是一样的,这说明方法名一样,就是一个东西
//只是编译器把iOS的@selector()转成了C的sel_registerName()这样的方法
objc_msgSend的执行流程可以分为3大阶段:
消息发送 -> 动态方法解析 -> 消息转发
下面,我们来详细说明一下:
2、消息发送
- 检查receiver是否有效,有效则开始查找方法;
- 先从receiverClass的cache中查找方法,找得到就跳到对应的函数去执行,如果cache中找不到就去receiverClass的class_rw_t中去查找;
- 如果还是找不到就依次去超类的cache中、class_rw_t中查找,直到找到NSObject类为止
- 如果还找不到就进入动态方法解析。
3、动态方法解析
#import "MJPerson.h"
#import
//method_t的结构如下
//可以利用method_t来定义方法,并且otherMethod->imp、otherMethod->types的方式取得imp、types、name
struct method_t {
SEL name;
char *types;
IMP imp;
};
@implementation MJPerson
//方法1:使用结构体method_t来定义方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(personTest)) {
//获取其他方法
struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//动态添加方法的实现
class_addMethod(self, sel, otherMethod->imp , otherMethod->types);
//返回YES
return YES;
}
return [super resolveInstanceMethod:sel];
}
//方法2:使用Method来定义方法,需要通过API取得方法的imp、types、name
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(personTest)) {
//动态添加方法的实现
Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//动态添加test方法的实现
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
//方法3:使用C语言函数
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(personTest)) {
//动态添加方法的实现
class_addMethod(self, sel, (IMP)c_otherTest, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
-(void)otherTest{
NSLog(@"Person otherTest");
}
void c_otherTest(id self,SEL _cmd){
NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
@end
上面代码中的resolveInstanceMethod是用来动态解析实例方法的,
类方法是用resolveClassMethod来动态解析的。
对于类方法,基本和实例方法一样,只是需要实现resolveClassMethod方法;另外在class_addMethod
中需要用object_getClass(self)
取得类对象,对类对象进行添加方法操作;
struct method_t {
SEL name;
char *types;
IMP imp;
};
//方法1:使用结构体method_t来定义方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(PersonClassTest)) {
//获取其他方法
struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//因为是MJPerosn这个类去调用,要用object_getClass(self)
class_addMethod(object_getClass(self), sel, otherMethod->imp , otherMethod->types);
//返回YES
return YES;
}
return [super resolveClassMethod:sel];
}
//方法2:使用Method来定义方法,需要通过API取得方法的imp、types、name
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(PersonClassTest)) {
Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//因为是MJPerosn这个类去调用,要用object_getClass(self)
class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
//方法3:使用C语言函数
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(PersonClassTest)) {
//因为是MJPerosn这个类去调用,要用object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_otherTest, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
-(void)otherTest{
NSLog(@"Person cl otherTest");
}
void c_otherTest(id self,SEL _cmd){
NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
- 如果事先没有动态解析,那便会通过resolveInstanceMethod / resolveClassMethod方法,对receiver动态添加方法,然后再进入消息发送;
- 如果之前已经有动态方法解析了,那进入消息转发。
4、消息转发
forwardingTargetForSelector方法
//程序中调用personTest
MJPerson *person = [[MJPerson alloc] init];
[person personTest];
//在person.m文件中实现:
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(personTest)) {
//返回MJCat,要求在MJCat中实现personTest方法
//就相当于调用了objc_msgSend([[MJCat alloc] init],aSelector);
return [[MJCat alloc] init];
//通过建立分类,在NSObject分类中实现personTest方法,也可以成功实现消息转发
//return [[NSObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
消息转发,如果forwardingTargetForSelector这个方法没有实现 或者 这个方法return nil的话。这种情况下,会调用methodSignatureForSelector方法
#pragma mark -无参的无返回值的可以用v@: 不一定非要写数字
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(personTest)) {
//返回方法签名:包含返回值类型、参数类型
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//也可以使用下面这句代替,这样就不用自己管Type Encodings了
//return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用:包含方法调用者、方法名、方法参数
// anInvocation.target ---> 方法调用者
// anInvocation.selector ---> 方法名字
// [anInvocation getArgument:NULL atIndex:0] ---> 方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"可以在这个方法中实现你想做的操作");
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
#pragma mark -对于有参,有返回值的,anInvocation中也有更多的功能
//ageTest:方法是传入int类型的age,然后乘以2再返回
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(ageTest:)) {
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
//也可以使用下面这句代替,这样就不用自己管Type Encodings了
//return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret]; //getReturnValue是获取方法返回值,anInvocation中还有其他更多的数据
NSLog(@"打印结果 %d",ret);
}
对于类方法来说,消息转发时,需要有点改变:就是要将调用的方法改成+开头,也就是类方法;另外消息接受者为类,而不是实例对象。
#pragma mark - - (id)forwardingTargetForSelector:(SEL)aSelector对应的
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(personTest)) {
//因为是调用类方法,所以这里要注意一下
return [MJCat class];
}
return [super forwardingTargetForSelector:aSelector];
}
当没有实现forwardingTargetForSelector方法或者这个方法返回nil时,methodSignatureForSelector方法,注意都是类方法,带+号的。
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(PersonClassTest)){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
//这里调用[MJCat class]这个类
[anInvocation invokeWithTarget:[MJCat class]];
}
5、总结
- <1> 介绍一下OC的消息机制
OC中的方法调用都是转成obje_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有三大阶段:
(1)消息发送(在当前类、父类中查找方法)
(2)动态方法解析
(3)消息转发
这三大阶段的具体流程可以根据流程图解释。