本页所使用的objc runtime 756.2,来自GITHUB
1、回顾
上文消息方法的本质3_消息查找流程 一文总结了方法查找的流程,得到结论如下:
- 类的缓存里查找——找到——结束查找
- 类的方法列表里查找——找到——结束查找
- 递归父类缓存、方法列表查找——找到——结束查找
- 动态方法解析——(未分析)
- 以上未解决——异常崩溃——结束
下面继续学习源码,结合业务代码,深挖第四步 动态方法决议,来更深一步的学习消息转发机制。
2、问题
这篇文章,希望弄清楚下面的问题
3.1 入口 - class_resolveMethod
之前的分析,知道了当查找缓存未命中后,会执行objc_msgSend_uncached
的方法,进行方法查找。
具体执行的是方法 _class_lookupMethodAndLoadCache3
。
runtime 源码如下:
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
在源码中,知道了具体执行的方法是 lookUpImpOrForward
,这个方法执行了众多流程,缓存、递归查找等。
如果都未命中,则会找到方法决议实现,代码如下:
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
注意到执行 _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)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
代码 | 分析 | 备注 |
---|---|---|
! cls->isMetaClass() | 是否元类 | 否,说明主体是实例 |
_class_resolveInstanceMethod | 实例方法决议实现 | |
_class_resolveClassMethod | 类方法决议实现 | |
_class_resolveInstanceMethod | [第2个] 查看NSObject 决议实现 | 发生在lookUpImpOrNil 失败后 |
分析:
进入决议后,先确定是否是元类;
如果是元类,由于已知类方法存储在元类里,知道该方法主体是类,则会进入类方法决议
如果不是元类,即方法主题是实例,进入实例方法决议。
如果元类方法决议查找失败,根元类亦查找失败,由于根元类的isa 是指向自身的,所以用到了
_class_resolveInstanceMethod
的实例决议方法。
具体逻辑见下图:
3.2 类方法决议 - _class_resolveClassMethod
捋清楚入口后,继续看类方法的决议,源码如下:
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
分析上文的源码,除了一些断言、异常的保护代码外,关键语句来到了这一句
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
这里的函数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{
return NO;
}
至此,已经大概明白了决议的意义——当方法找不到实现时,转发到类/实例决议方法里,看看开发者是否有自行实现。其最大的意义,恐怕是收集各种崩溃了。
3.3 实例方法决议 - class_resolveInstanceMethod
实例方法决议的源码实现,与类方法决议类似,差别仅仅在决议执行方法,换成了SEL_resolveInstanceMethod
,源码如下:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
当决议方法得到实现后,会提示,方法XX 已经被决议成为新的实现YY—— 否则会提示决议未找到:
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
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 {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
7、 总结
又到了最受欢迎的总结时间。
答案:
- 消息转发是什么:向指定的对象或类,发送需要消息,请求交由对方执行
- 消息转发的场景:自我无法实现的场景下。
- 如何进行消息转发
- 快速转发:使用
forwardingTargetForSelector
进行定向转发- 慢速转发:获取签名,使用
forwardInvocation
转发
有图
另外,总结经验再多,不如有图有真相……
下面是objc_msgSend 查找总流程: