导语:通过对OC对象,类对象,元类对象结构的一些简单理解,方法调用流程的了解,我们可以更简单看懂消息机制的发送消息阶段是怎么样搜索方法的。
OC的消息机制
- OC中的方法调用有别于其他语言的函数调用机制(地址传递调用函数),是通过消息机制,所有的方法调用都会在Runtime运行期通过objc_msgsend这个函数完成。
比如,
[person eatWithFood:@[@"rice",@"fruits"]]
。可以理解为系统给person这个对象发送了一条eatWithFood消息。
让person找到对应方法实现,执行其代码。
- 系统调用
objc_msgSend(id self, SEL, cmd, ...)
- 会把person作为函数的第一个参数,称为消息的“接收者”
- eatWithFood这个方法会作为第二个参数,可以称为“选择子”
- eatWithFood方法的参数做为objc_msgSend函数后面的参数按顺序传入
- objc_msgSend在底层分三个阶段执行:
- 消息发送(在当前类对象,父类类对象中查找对应方法)
- 动态方法解析
- 消息转发
消息发送
- 首先,在接收者所属类中查找对应方法,如果查不到就按照继承体系往上查找,找到该方法后跳至代码实现。
//class:类对象 meta-class:元类对象
对象方法查找路径:class -> super class -> ...-> root class
类方法查找路径:meta-class -> super meta-class ->...-> root meta-class -> root class
复制代码
方法查找具体步骤 |
---|
1. 在类的方法缓存cache 中查找,有就调用,没有就在本类的class_rw_t 中查找,有就调用,并且会把方法缓存到cache 中方便下次查找。 |
2. 如果还是没有找到,就到父类中重复同样的操作,只要查找到方法都会在调用方法并且把方法缓存到自己类的cache 中。 |
3. 如果在执行完消息发送,还是没有找到方法就进入动态方法解析阶段。 |
动态方法解析:
- 经过消息发送并没查找到对应方法,Runtime就会征询接收者,是否可以动态添加方法,处理当前选择子,这个阶段就是“动态方法解析”。
1.对象在收到无法解读的消息后,会先调用类方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
参数sel:就是选择子,未查找到的方法
复制代码
表示询问是否可以新增一个方法处理此选择子。
2.添加方法可以使用
class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
参数1:cls 就是消息接收者
参数2:name 就是sel选择子(找不到实现的方法)
参数3:imp 代表函数指针(动态添加的方法)
参数4:types 就是Type Encoding,字符串编码
复制代码
- 例子:
//- (void)eatWithFood:(NSArray *)foods{
// NSLog(@"I eated %@ for lunch",foods);
//}
#pragma mark - 动态方法解析
//method_t是底层结构体,并没有暴露出来不可直接使用,我们模仿底层写一个
struct method_t{
SEL sel;
char *types;
IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (@selector(eatWithFood:) == sel) {
//1.通过模仿底层的method_t强转来做方法添加
// //强转
// struct method_t *newMethod = (struct method_t *)class_getInstanceMethod(self, @selector(eatLuch:));
//
// //新添加方法
// class_addMethod(self, sel, newMethod->imp, newMethod->types);
//2.调用系统现成接口
// Method method = class_getInstanceMethod(self, @selector(eatLuch:));
//
// class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
//3.调用现成接口(3跟2不同的是直接获取了imp)
IMP imp = class_getMethodImplementation(self, @selector(eatLuch:));
Method method = class_getInstanceMethod(self, @selector(eatLuch:));
const char *types = method_getTypeEncoding(method);
class_addMethod(self, sel, imp, types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
//添加的方法实现
- (void)eatLuch:(NSArray *)foods{
NSLog(@"午饭吃了%@", foods);
}
复制代码
- 也可以添加c语言函数实现
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eatWithFood:)) {
class_addMethod(self, sel, (IMP)eatSomeFoods, "v24@0:8@16");
}
return [super resolveInstanceMethod:sel];
}
//c语言函数
void eatSomeFoods(id self, SEL _cmd, NSArray *foods){
NSLog(@"午饭吃了---%@",foods);
}
复制代码
- 以上的动态方法解析,都是针对对象方法举例的,如果是类方法,调用以下方法
+ (BOOL)resolveClassMethod:(SEL)sel
复制代码
- class_addMethod有个参数叫types 就是Type Encoding,字符串编码。如果不清楚可以查看苹果官方文档。
消息转发
- 如果消息接收者没有允许动态添加方法,就进入到消息转发阶段,此阶段也分为两个阶段:
1.请接收者查看有没有其他对象处理该方法,若有就转发消息给该对象处理。
2.如果没其他对象处理消息,就启动完整的消息转发机制,runtime会把与此消息有关的所有细节都封装到NSInvocation对象中,给接收者最后一次处理数据的机会,令其设法处理该消息。
备援接收者
如果征询到类无法动态添加方法,就会请接收者查看是否有其他类来处理该选择子,如果有这个对象就是备援接收者。
该步骤系统会调用以下方法来处理:
- (id)forwardingTargetForSelector:(SEL)aSelector
只要返回一个可以处理方法的对象就可以了
复制代码
例如:
FRPerson.m
- (id)forwardingTargetForSelector:(SEL)aSelector{
//直接返回一个可以处理该选择子的对象,然后在该对象内部实现该方法
if (aSelector == @selector(eatWithFood:)) {
return [[FRStudent alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
FRStudent.m
#import "FRStudent.h"
@implementation FRStudent
- (void)eatWithFood:(NSArray *)foods{
NSLog(@"学生午饭吃了 %@ ",foods);
}
@end
复制代码
在本例子中,FRStudent处理了消息,消息转发到此为止。
完整的消息转发机制
- 如果没有其他接收者处理消息,进入完整的消息转发机制
- 会把所有相关信息封装到NSInvocation类中
- 通过forwardInvocation方法发给目标对象
- 系统会调用以下方法:
//返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
1.如果方法签名返回nil,就表明放弃最后处理方法的机会,系统抛出错误
2.返回了方法签名之后,触发forwardInvocation方法
复制代码
//触发forwardInvocation之后,可以在此方法实现里做任何操作
- (void)forwardInvocation:(NSInvocation *)anInvocation
复制代码
例如:
//返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(eatWithFood:)) {
return [NSMethodSignature signatureWithObjCTypes:"v24@0:8@16"];
}
return nil;
}
//methodSignatureForSelector方法返回nil,forwardInvocation就不会被调用
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if (anInvocation.selector == @selector(eatWithFood:)) {
//1.可以转发个多个对象处理消息
// [anInvocation invokeWithTarget:[[NSClassFromString(@"FRStudent") alloc] init]];
//
// [anInvocation invokeWithTarget:[[NSClassFromString(@"FRCat") alloc] init]];
//2.可以修改target调用者,也就是改变对象处理消息
// anInvocation.target = NSClassFromString(@"FRStudent");
// [anInvocation invoke];
//3.来到这个方法之后也可以不处理那个没有实现的方法,可以做其他与这个方法毫无关系的其他事情,还可以什么都不做
}
}
复制代码
- 注意:进入方法转发阶段的三个方法都有类方法类型,用来处理调用类方法。
+ (id)forwardingTargetForSelector:(SEL)aSelector
复制代码
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
复制代码
+ (void)forwardInvocation:(NSInvocation *)anInvocation
复制代码
如果xcode没有提示,可以先敲出对象方法,把“-”改为“+”就好。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END