在前面的两篇博客iOS原理探索08--objc_msgSend慢速查找流程分析和iOS原理探索07--objc_msgSend快速查找流程分析中,我们知道方法的调用查找流程:先在缓存中进行快速查找
,如果快速查找没有找到
,那么会进入慢速查找流
,在方法的列表中进行查找
,在这慢速查找流程结束后
,没有找到
的时候,会执行一次动态决议方法
,如果动态决议
还没有
找到,会进行消息转发
。如果消息转发也没有那么就会来到我们平时开发中的unrecognized selector sent to instance
报错提示!
日常开发中没有方法实现的报错分析
- 条件设置:创建一个
LGPerson
类,添加一个+(void)sayNB
方法,但是并没有实现该方法;在main函数
中通过初始化一个LGPerson
类对象,调用sayNB
方法,运行程序。
可以看到,这里已经报错了。下面可以跟进源码来看一下报错的源代码实现。
- 源代码实现
根据慢速查找
发现,报错都走到了根据慢速查找的源码,我们发现,其报错最后都是走到__objc_msgForward_impcache方法
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
- 汇编实现中查找
__objc_forward_handler
,并没有找到该方法的实现,那么在源码中去掉一个下划线进行全局搜索_objc_forward_handler
,最后发现默认执行的是objc_defaultForwardHandler
方法。下面是源码实现。
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
打印输出的就是如上方法为找到的错误提示。
动态决议
- 苹果建议在慢速查找没有找到方法实现的时候,使用动态决议方法,可以算是补救崩溃的一个机会吧。
- 这个补救崩溃的机会就在
lookUpImpOrForward
方法中
//如果没有找到方法实现,尝试方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//动态方法决议的控制条件,表示流程只走一次
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
-
resolveMethod_locked
源码实现
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
//对象 -- 类
if (! cls->isMetaClass()) { //类不是元类,调用对象的解析方法
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {//如果是元类,调用类的解析方法, 类 -- 元类
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
//为什么要有这行代码? -- 类方法在元类中是对象方法,所以还是需要查询元类中对象方法的动态方法决议
if (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
//如果方法解析中将其实现指向其他方法,则继续走方法查找流程
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
所以如果没有实现的方法,我们可以通过resolveInstanceMethod
进行一次补救,
- 主要流程分为以下几个步骤
类不是元类,调用对象的解析方法,执行的是
-(void) resolveInstanceMethod(id inst, SEL sel, Class cls)
方法;
如果是元类,调用类的解析方法+ (BOOL)resolveClassMethod:(SEL)sel
, 即类 -- 元类
-
分析流程图
- 实例方法的动态决议
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// look的是 resolveInstanceMethod --相当于是发送消息前的容错处理
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel); //发送resolve_sel消息
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//查找say666
IMP imp = lookUpImpOrNil(inst, sel, cls);
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));
}
}
}
主要分为以下几个步骤:
在发送
resolveInstanceMethod
消息前,需要查找cls类
中是否有该方法的实现
,即通过lookUpImpOrNil方法
又会进入lookUpImpOrForward慢速查找流程
查找resolveInstanceMethod方法
如果
没有
,则直接返回
如果
有
,则发送resolveInstanceMethod消息
再次慢速查找
实例方法的实现,即通过lookUpImpOrNil
方法又会进入lookUpImpOrForward
慢速查找流程查找实例方法实例方法示例:
say666
未实现的方法,通过resolveInstanceMethod
动态决议后由sayMaster
进行实现。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
//获取sayMaster的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
//获取sayMaster的丰富签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
- 类方法的动态决议
解决未实现类方法调用崩溃问题,比如上面的+(void)sayNB;
方法添加如下代码
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
LGPerson
打印了lgClassMethod
方法。注意:resolveClassMethod
类方法的重写需要注意一点,传入的cls不再是类
,而是元类
,可以通过objc_getMetaClass方法
获取类的元类,原因是因为类方法在元类中是实例方法
。
动态决议相关优化
我们通过继承链可以知道,实例方法:类 -- 父类 -- 根类 -- nil
,类方法元类 -- 根元类 -- 根类 -- nil
。
那么我们可以将动态决议方法,放在
NSObject的分类
里面,统一实现未实现的方法。当然我们需要注意的是,这样会拦截系统的方法,我们可以按照项目模块针对自定义类中方法统一方法名的前缀
判断。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say666)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMethod);
return class_addMethod(self, sel, imp, type);
}else if (sel == @selector(sayNB)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return NO;
}
消息转发
如果方法的快速查找、慢速查找以及动态决议都找不到的情况下,就会进行一个消息转发,我们可以利用消息转发来做一些操作避免出现崩溃,这同样是苹果给我们的一个避免发生错误的机会。
- 通过
instrumentObjcMessageSends
的方式查看发送消息的日志
通过
lookUpImpOrForward --> log_and_fill_cache --> logMessageSend
,在logMessageSend
源码下方找到instrumentObjcMessageSends
的源码实现,所以,在main
中调用instrumentObjcMessageSends
打印方法调用的日志信息,有以下两点准备工作
1、打开objcMsgLogEnabled 开关
,即调用instrumentObjcMessageSends
方法时,传入YES
2、在main中
通过extern
声明instrumentObjcMessageSends
方法
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
NSLog(@"Hello, World!");
}
return 0;
}
可以根据源码提供的路径/tmp/msgSends
查看日志
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
运行程序崩溃,按照路径可以找到如下文件
-
崩溃日志路径
-
崩溃日志内容
打开日志我们可以看到
- 两次动态方法决议:
resolveInstanceMethod方法
- 两次消息快速转发:
forwardingTargetForSelector方法
- 两次消息慢速转发:
methodSignatureForSelector + resolveInstanceMethod
- 使用
bt
查看堆栈信息 - 发现
___forwarding___来自CoreFoundation
-
image list
读取整个镜像文件,然后搜索CoreFoundation
,查看其可执行文件的路径如下图所示 - 根据
CoreFoundation
路径找到该文件 - 通过反汇编工具
hopper
查看
搜索
__forwarding_prep_0___
方法,通过跳转____forwarding___
,我们可以看到该方法的伪代码了,如下所示
通过操作我们可以先判断是否实现了forwardingTargetForSelector
方法,如果没有响应,跳转至loc_64a67
也就是如果快速转发没有响应,则进入慢速转发流程,查看是否实现methodSignatureForSelector
方法,如果没有响应,跳转至loc_64e3
,则直接报错,如果获取methodSignatureForSelector
的方法签名为nil
,也是直接报错
如果methodSignatureForSelector
返回值不为空
,则在forwardInvocation
方法中对invocation
进行处理
-
消息转发机制流程图
消息转发的处理主要分为两部分:
【快速转发】当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走forwardingTargetForSelector方法
如果返回消息接收者,在消息接收者中
还是没有找到
,则进入另一个
方法的查找流程
如果返回nil
,则进入慢速消息转发
。
【慢速转发】执行到methodSignatureForSelector方法
,如果返回的方法签名为nil
,则直接崩溃报错
如果返回的方法签名
不为nil
,走到forwardInvocation
方法中,对invocation事务
进行处理,如果不处理也不会报错
总结:消息转发有三种
-
forwardingTargetForSelector
快速转发
如果动态决议没有找到方法,则需要在LGPerson中
重写forwardingTargetForSelector
方法,将LGPerson的实例方法
的接收者
指定为LGStudent的对象
,代码如下
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
//将消息的接收者指定为LGStudent,在LGStudent中查找say666的实现
return [LGStudent alloc];
}
输出结果如下所示-
methodSignatureForSelector
和forwardInvocation
慢速转发
如果快速转发没有找到方法的实现,就会进行慢速转发流程,代码如下
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
// GM sayHello - anInvocation - 漂流瓶 - anInvocation
anInvocation.target = [LGStudent alloc];
// anInvocation 保存 - 方法
[anInvocation invoke];
}
输出结果如下所示,并且发现forwardInvocation方法中
不对invocation
进行处理,也不会
崩溃报错
。