本页所使用的objc runtime 756.2,来自GITHUB
1、回顾
上文消息方法的本质3_消息查找流程 一文总结了方法查找的流程,得到结论如下:
- 类的缓存里查找——找到——结束查找
- 类的方法列表里查找——找到——结束查找
- 递归父类缓存、方法列表查找——找到——结束查找
- 动态方法解析——(未分析)
- 以上未解决——异常崩溃——结束
下面继续学习源码,结合业务代码,深挖第四步 动态方法决议,来更深一步的学习消息转发机制。
2、问题
这篇文章,希望弄清楚下面的问题
- 消息转发是什么
- 消息转发的场景
- 如何进行消息转发
3、动态方法决议
3.1 入口 - class_resolveMethod
之前的分析,知道了当查找缓存未命中后,会执行objc_msgSend_uncached
的方法,进行方法查找。
具体执行的是方法 _class_lookupMethodAndLoadCache3
。
runtime 源码如下:
/*********************************************************************** |
在源码中,知道了具体执行的方法是 lookUpImpOrForward
,这个方法执行了众多流程,缓存、递归查找等。
如果都未命中,则会找到方法决议实现,代码如下:
// No implementation found. Try method resolver once. |
注意到执行 _class_resolveMethod(cls, sel, inst)
前后,使用了runtimeLock 进行了锁定与解锁,具体实现时互斥锁(参见Cooci老师的互斥锁-读写锁-条件锁),来保证内存安全。
查看一下class_resolveMethod 方法
Summary
_class_resolveMethod Call +resolveClassMethod or +resolveInstanceMethod. Returns nothing; any result would be potentially out-of-date already. Does not check if the method already exists.
class_resolveMethod 调用 +resolveClassMethod 或 +resolveInstanceMethod。
返回值为空;任何结果将可能已经过期。
如果方法已经存在,不会检查/执行。
由此可见resolveMethod 只是一个入口,会具体调用如下方法:
- 类方法:+resolveInstanceMethod
- 实例方法:+resolveInstanceMethod
具体的源码,见下面的:
void _class_resolveMethod(Class cls, SEL sel, id inst) |
代码 | 分析 | 备注 |
---|---|---|
! cls->isMetaClass() | 是否元类 | 否,说明主体是实例 |
_class_resolveInstanceMethod | 实例方法决议实现 | |
_class_resolveClassMethod | 类方法决议实现 | |
_class_resolveInstanceMethod | [第2个] 查看NSObject 决议实现 | 发生在lookUpImpOrNil 失败后 |
分析:
进入决议后,先确定是否是元类;
如果是元类,由于已知类方法存储在元类里,知道该方法主体是类,则会进入类方法决议
如果不是元类,即方法主题是实例,进入实例方法决议。
如果元类方法决议查找失败,根元类亦查找失败,由于根元类的isa 是指向自身的,所以用到了
_class_resolveInstanceMethod
的实例决议方法。
具体逻辑见下图:
3.2 类方法决议 - _class_resolveClassMethod
捋清楚入口后,继续看类方法的决议,源码如下:
/*********************************************************************** |
分析上文的源码,除了一些断言、异常的保护代码外,关键语句来到了这一句
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; |
这里的函数msg
调用了3个参数
- _class_getNonMetaClass(cls, inst) :即类的本身
- SEL_resolveClassMethod : 决议的类方法
- sel :需要查找的方法编号
所以,关键的行为就是SEL_resolveClassMethod
方法,通过搜索,源码代码中均找不到。
回头看苹果给该方法写的注释:
_class_resolveClassMethod
Call +resolveClassMethod, looking for a method to be added to class cls.
意思是C 源码里写的方法,在OC 实现里,会执行+resolveClassMethod 的方法,即通过resolveClassMethod 来对该类进行决议——添加相应的类方法。
在OC里的实现即这个方法
+ (BOOL)resolveClassMethod:(SEL)sel{ |
至此,已经大概明白了决议的意义——当方法找不到实现时,转发到类/实例决议方法里,看看开发者是否有自行实现。其最大的意义,恐怕是收集各种崩溃了。
3.3 实例方法决议 - class_resolveInstanceMethod
实例方法决议的源码实现,与类方法决议类似,差别仅仅在决议执行方法,换成了SEL_resolveInstanceMethod
,源码如下:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; |
当决议方法得到实现后,会提示,方法XX 已经被决议成为新的实现YY—— 否则会提示决议未找到:
if (resolved && PrintResolving) { |
4、快速转发阶段
4.1 快速转发方法
forwardingTargetForSelector :即将本类未实现的方法,交由指定的其他类来实现。
4.2 业务代码
实现如下:
类
Person
, 与未实现的方法teach
:@interface Person : NSObject
- (void)teach;
@end类的转发方法——指定
Teacher
这个类来执行- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(teach)) {
return [Teacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}类
Teacher
以及实现的方法teach
NS_ASSUME_NONNULL_BEGIN
@interface Teacher : NSObject
- (void)teach;
@end
NS_ASSUME_NONNULL_END
@implementation Teacher
- (void)teach{
NSLog(@"%s", __func__);
}
@end主业务执行,使类执行它为实现的方法
teach
:int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person teach];
}
return 0;
}执行结果,
teacher
类实现了teach
:2020-03-20 16:12:51.477020+0800 debug-objc[8565:349779] -[Teacher teach]
由此可见,forwardingTargetForSelector 的作用,是自身无法处理,交由其他对象(可能是类)来处理。
5、慢速转发阶段
5.1 慢速转发方法
methodSignatureForSelector,看看开发文档的描述:
methodSignatureForSelector:
Returns an
NSMethodSignature
object that contains a description of the method identified by a given selector.返回一个
NSMethodSignature
的签名对象,包含一个给定选择器的方法的描述
- 获取方法签名:
methodSignatureForSelector
- 通过获取的签名,转发信息 –
执行方法是forwardInvocation
往指定的对象传递方法和参数
5.2 业务代码实现:
基础代码与快速转发一致,只是删除了forwardingTargetForSelector 的实现代码。
并在Person
类里实现如下代码
实现方法签名
// 获得方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *sel = NSStringFromSelector(aSelector);
//生成签名
if ([sel isEqualToString:@"run"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}else {
return [super methodSignatureForSelector:aSelector];
}
}“v” 是方法描述,更多的在这里:
* 代表 char *
char BOOL 代表 c
: 代表 SEL
^type 代表 type *
@ 代表 NSObject * 或 id
^@ 代表 NSError **
# 代表 NSObject
v 代表 void
获取签名,进行配发信息
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"-----%@-----",anInvocation);
//取得消息
SEL selector = [anInvocation selector];
//转发
SomePerson *someP = [SomePerson new];
if ([someP respondsToSelector:selector]) {
//调用对象,进行转发
[anInvocation invokeWithTarget:someP];
} else {
return [super forwardInvocation:anInvocation];
}
}执行
teach
结果2020-03-20 16:37:41.639718+0800 debug-objc[9325:370827] -[Teacher teach]
6、查找失败
消息没有得到处理 崩溃退出,这里从forwardInvocation
的实现,可以追踪到相关的代码:
- (void)forwardInvocation:(NSInvocation *)invocation { |
// Replaced by CF (throws an NSException) |
7、 总结
又到了最受欢迎的总结时间。
答案:
- 消息转发是什么:向指定的对象或类,发送需要消息,请求交由对方执行
- 消息转发的场景:自我无法实现的场景下。
- 如何进行消息转发
- 快速转发:使用
forwardingTargetForSelector
进行定向转发- 慢速转发:获取签名,使用
forwardInvocation
转发
有图
另外,总结经验再多,不如有图有真相……
下面是objc_msgSend 查找总流程: