目录
- 简介
- 消息发送objc_msgSend
- 动态方法解析
- 消息转发
---- 转发接收者
---- 转发调用- 补充
---- 方法签名
---- 类方法消息转发
---- invocation
---- @dynamic
简介
OC方法调用,其实都是发送消息,底层使用objc_msgSend
进行发送。主要分为三个阶段:
- 第一阶段:消息发送
- 第二阶段:动态方法解析
- 第三阶段:消息转发
消息发送objc_msgSend
调用方法底层是通过objc_msgSend实现的。
objc_msgSend
方法中有两个参数,一个是消息接收者(receiver)
,一个是消息名称
。
[person run];
底层实现
objc_msgSend(person, sel_registerName("run"))
为了方便阅读可以写成:
objc_msgSend(person, @selector(run))
objc_msgSend源码是由汇编和C++代码实现的,消息发送过程如下:
动态方法解析
- 当消息发送阶段结束,没有找到方法,就会进入动态方法解析阶段。
- 系统给实例方法提供了
resolveInstanceMethod
方法,给类方法提供了resolveClassMethod
方法,给没找到的方法添加实现的机会。- 我们可以实现
resolveInstanceMethod
和resolveClassMethod
,来动态添加方法实现。
比如person实例对象调用了run方法,但是run方法没有实现,可以按照下面方式给run方法添加实现。
- (void)other {
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s",__func__);
// 动态添加run方法实现
if (sel == @selector(run)) {
// 获取其它方法
Method method = class_getInstanceMethod(self, @selector(other));
// 给sel(run)添加方法实现
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
也可以添加c方法实现:
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other-%@-%@",self,NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s",__func__);
// 动态添加run方法实现
if (sel == @selector(run)) {
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
类方法原理相同,注意添加方法的对象应传入类对象:
+ (BOOL)resolveClassMethod:(SEL)sel {
// 动态添加run方法实现
if (sel == @selector(run)) {
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
动态解析过程如下:
动态解析过后,会重新走“消息发送”的流程。从receiverClass的cache中查找方法
这一步开始执行。
消息转发
-
转发接收者
动态方法解析失败后会开始快速转发流程,指定一个能处理该方法的接收者,让接收者执行此方法。比如将Person的run方法,转发给Cat执行run方法:
// 转发接收者,指定一个新的消息接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [[Cat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
-
转发调用
转发接收者失败,会进入转发调用流程,先返回一个转发方法签名,根据这个方法签名系统会创建一个NSInvocation
对象,用于处理转发工作。
// 返回方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括方法调用者、方法名、方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:[[Cat alloc] init]];
}
消息转发过程如下:
我们可以在forwardInvocation:
方法中自定义任何逻辑,比如拦截过滤等。
以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
补充
-
方法签名
v16@0:8这样的类型编码作为返回值,可以不写数字:
// 返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
也可以通过创建接收者实例对象找到对应的方法签名:
// 返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [[[Cat alloc] init] methodSignatureForSelector:@selector(run)];
}
return [super methodSignatureForSelector:aSelector];
}
-
类方法消息转发
需要将消息转发方法换位类方法,并注意返回对象为类对象,转发接收者:
// 转发接收者
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [Cat class];
}
return [super forwardingTargetForSelector:aSelector];
}
转发调用:
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"......");
}
-
invocation
当消息转发流程进入forwardInvocation:方法后,可以利用invocation获取方法参数、获取方法返回值等数据。
-
@dynamic
@dynamic就是通知编译器,不要给age自动生成setter和getter方法的实现,不自动生成员变量。(不影响setter/getter声明)
@dynamic age;
添加后调用person.age会报错,提示找不到方法。
网友评论