Method的结构体
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
- 方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
- 方法类型 method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。(即类型编码)
- method_imp 指向方法的实现,本质上是一个函数的指针,就是前面讲到的 Implementation。
因此,上述以OC形式展现出来的函数就会转化成如下函数:
id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
可以看出,在调用方法时,编译器将它转成了objc_msgSend消息发送了,在Runtime的执行过程如下
- 1、Runtime先通过对象
someobject找到isa指针,判断isa指针是否为nil,为nil直接return。 - 2、若不为空则通过
isa指针找到当前实例的类对象,在类对象下查找缓存是否有messageName方法。 - 3、若在类对象缓存中找到
messageName方法,则直接调用IMP方法(本质上是函数的指针)。 - 4、若在类对象缓存中没找到
messageName方法,则查找当前类对象的方法列表methodlist,若找到方法则将其添加到类对象的缓存中。 - 5、若在类对象方法列表中没找到
messageName方法,则继续到当前类的父类中以相同的方式查找(即类的缓存->类的方法列表)。 - 6、若在父类中找到
messageName方法,则将IMP添加到类对象缓存中。 - 7、若在父类中没找到
messageName方法,则继续查询父类的父类,直到追溯到最上层NSObject。 - 8、若还是没有找到,则启用动态方法解析、备用接收者、消息转发三部曲,给程序最后一个机会
- 9、若还是没找到,则Runtime会抛出异常
doesNotRecognizeSelector。
消息动态解析
Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。前者在 对象方法未找到时 调用,后者在 类方法未找到时 调用。我们可以通过重写这两个方法,添加其他函数实现,并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。
主要用的的方法如下:
// 类方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/**
* class_addMethod 向具有给定名称和实现的类中添加新方法
* @param cls 被添加方法的类
* @param name selector 方法名
* @param imp 实现方法的函数指针
* @param types imp 指向函数的返回值与参数类型
* @return 如果添加方法成功返回 YES,否则返回 NO
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char * _Nullable types);
测试代码
main.m 文件中
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
Person *xiaoming = [[Person alloc]init];
[xiaoming performSelector:@selector(way)];
return 0;
}
person.m 文件中
// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(way)) {
class_addMethod([self class], sel,class_getMethodImplementation([self class], @selector(method)), "123");//使用class_addMethod动态添加方法method
}
return YES;
}
- (void)method{
NSLog(@"进入了消息动态解析");
}
运行了上述的测试代码后,我们会发现即便我们并没有实现way方法,而且使用了performSelector去强行调用way方法,但是我们的程序并没有崩溃,是因为在查找了类方法和所有的父类后还是没有找到way方法,程序进入了消息动态解析,然后我们使用了class_addMethod去动态添加方法method,最后程序从调用
performSelector和直接调用方法的区别
performSelector: withObject:是在iOS中的一种方法调用方式。他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式。
所以performSelector和直接调用方法的区别就在与runtime。直接调用编译是会自动校验。如果方法不存在,那么直接调用 在编译时候就能够发现,编译器会直接报错。
但是使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以一般使用performSelector的时候,一般都会使用- (BOOL)respondsToSelector:(SEL)aSelector;来在运行时判断对象是否响应此方法。
消息接受者重定向(备用接受者)
如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。
如果当前对象实现了 -forwardingTargetForSelector: 或者 +forwardingTargetForSelector: 方法,Runtime 就会调用这个方法,允许我们将消息的接受者转发给其他对象。
其中用到的方法。
// 重定向类方法的消息接收者,返回一个类或实例对象
+ (id)forwardingTargetForSelector:(SEL)aSelector;
// 重定向方法的消息接收者,返回一个类或实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector;
注意:
- 类方法和对象方法消息转发第二步调用的方法不一样,前者是
+forwardingTargetForSelector:方法,后者是-forwardingTargetForSelector:方法。- 这里
+resolveInstanceMethod:或者+resolveClassMethod:无论是返回YES,还是返回NO,只要其中没有添加其他函数实现,运行时都会进行下一步。
测试代码
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(way)) {
Friends *friends = [[Friends alloc]init];
return friends;//返回friends对象,让friends对象接受这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
可以看到,虽然当前 person 没有实现 fun 方法,+resolveInstanceMethod: 也没有添加其他函数实现。但是我们通过 forwardingTargetForSelector 把当前 person 的方法转发给了 friends 对象去执行了。打印结果也证明我们成功实现了转发。
我们通过 forwardingTargetForSelector 可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是不是 nil,也不是 self,系统会将运行的消息转发给这个对象执行。否则,继续进行下一步:消息重定向流程。
消息重定向
如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 或者 +methodSignatureForSelector: 方法获取函数的参数和返回值类型。
- 如果
methodSignatureForSelector:返回了一个NSMethodSignature对象(函数签名),Runtime 系统就会创建一个NSInvocation对象,并通过forwardInvocation:消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。 - 如果
methodSignatureForSelector:返回nil。则 Runtime 系统会发出doesNotRecognizeSelector:消息,程序也就崩溃了。
所以我们可以在 forwardInvocation: 方法中对消息进行转发。
注意:类方法和对象方法消息转发第三步调用的方法同样不一样。
类方法调用的是:
+ methodSignatureForSelector:+ forwardInvocation:+ doesNotRecognizeSelector:对象方法调用的是:
- methodSignatureForSelector:- forwardInvocation:- doesNotRecognizeSelector:
用到的方法:
// 获取类方法函数的参数和返回值类型,返回签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 类方法消息重定向
+ (void)forwardInvocation:(NSInvocation *)anInvocation;
// 获取对象方法函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 对象方法消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(way)) {
return [NSMethodSignature methodSignatureForSelector:@selector(way)];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Friends *f = [[Friends alloc] init];
if([f respondsToSelector:sel]) { // 判断 Person 对象方法是否可以响应 sel
[anInvocation invokeWithTarget:f]; // 若可以响应,则将消息转发给其他对象处理
} else {
[self doesNotRecognizeSelector:sel]; // 若仍然无法响应,则报错:找不到响应方法
}
}
既然 -forwardingTargetForSelector: 和 -forwardInvocation: 都可以将消息转发给其他对象处理,那么两者的区别在哪?
区别就在于 -forwardingTargetForSelector: 只能将消息转发给一个对象。而 -forwardInvocation: 可以将消息转发给多个对象。
以上就是 Runtime 消息转发的整个流程。
消息发送以及转发机制总结
调用 [receiver selector]; 后,进行的流程:
-
编译阶段:
[receiver selector];方法被编译器转换为:objc_msgSend(receiver,selector)(不带参数)objc_msgSend(recevier,selector,org1,org2,…)(带参数)
-
运行时阶段:消息接受者
recevier寻找对应的selector- 通过
recevier的isa 指针找到recevier的class(类); - 在
Class(类)的cache(方法缓存)的散列表中寻找对应的IMP(方法实现); - 如果在
cache(方法缓存)中没有找到对应的IMP(方法实现)的话,就继续在Class(类)的method list(方法列表)中找对应的selector,如果找到,填充到cache(方法缓存)中,并返回selector; - 如果在
class(类)中没有找到这个selector,就继续在它的superclass(父类)中寻找; - 一旦找到对应的
selector,直接执行recevier对应selector方法实现的IMP(方法实现)。 - 若找不到对应的
selector,Runtime 系统进入消息转发机制。
- 通过
-
运行时消息转发阶段:
- 动态解析:通过重写
+resolveInstanceMethod:或者+resolveClassMethod:方法,利用class_addMethod方法添加其他函数实现; - 消息接受者重定向:如果上一步添加其他函数实现,可在当前对象中利用
forwardingTargetForSelector:方法将消息的接受者转发给其他对象; - 消息重定向:如果上一步没有返回值为
nil,则利用methodSignatureForSelector:方法获取函数的参数和返回值类型。- 如果
methodSignatureForSelector:返回了一个NSMethodSignature对象(函数签名),Runtime 系统就会创建一个NSInvocation对象,并通过forwardInvocation:消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。 - 如果
methodSignatureForSelector:返回nil。则 Runtime 系统会发出doesNotRecognizeSelector:消息,程序也就崩溃了。
- 如果
- 动态解析:通过重写
Objective-C Runtime 消息发送与转发机制详解
本文详细介绍了Objective-C中方法结构体Method的组成,包括方法名、方法类型和方法实现。接着阐述了消息发送的流程,从对象的isa指针查找,到方法缓存、方法列表、父类查找,再到动态方法解析和消息接受者重定向。重点讲解了消息转发的三个步骤:动态解析、消息接受者重定向和消息重定向,以及在每个步骤中如何处理未找到的方法。最后,总结了消息发送及转发的完整流程,帮助理解Objective-C的Runtime机制。
3473

被折叠的 条评论
为什么被折叠?



