前提
在前面两篇文章OC底层探索(八)objc_msgSend 流程之方法快速查找和OC底层探索(九)objc_msgSend 流程之方法慢速查找中,分别分析了objc_msgSend的快速查找和慢速查找,在这两种都没找到方法实现的情况下,苹果给了两个建议
- 动态方法决议:慢速查找流程未找到后,会执行一次动态方法决议
- 消息转发:如果动态方法决议仍然没有找到实现,则进行消息转发
环境准备
新建Person类
Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;
+ (void)sayNB;
+ (void)lgClassMethod;
@end
Person.m
@implementation Person
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayNB{
NSLog(@"%s",__func__);
}
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
@end
main.m文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [Person alloc];
//调用实例方法
[person say666];
//调用类方法
[Person sayNB];
}
return 0;
}
动态方法决议【第一次机会】
在文章OC底层探索(九)objc_msgSend 流程之方法慢速查找中,我们介绍慢速查找的时候,没有找到imp,那么苹果就会给一次机会,挽救一下。—— 【第一次机会】
源码
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
· 如果是元类,执行类方法的动态方法决议resolveClassMethod,如果在元类中没有找到或者为空,则在元类的实例方法的动态方法决议resolveInstanceMethod中查找,主要是因为类方法在元类中是实例方法,所以还需要查找元类中实例方法的动态方法决议 - 如果动态方法决议中,将其实现指向了其他方法,则继续查找指定的imp,即继续慢速查找lookUpImpOrForward流程
调用未实现的实例方法,查看打印
- 在main.m文件中调用person的say666方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [Person alloc];
[person say666];
[Person sayNB];
}
return 0;
}
-
打印结果
调用实例方法say666报的错
调用类方法sayNB报的错
-
分析
由于在快速查找和慢速查找时,都没有找到say666实例方法和sayNB类方法的实现,所以程序崩溃。 -
resolveInstanceMethod的源码
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// lookup 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);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
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));
}
}
}
实例方法
第一次补救
- 在person.m文件中重写resolveInstanceMethod方法,先return父类的此方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ say666来了",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
打印结果:
分析
· 在执行实例方法say666时,在快速、慢速查找都没有找到的情况下,就会执行到resolveInstanceMethod方法。但是我们查看打印,执行了两遍这个方法,第一次执行是在慢速查找结束后执行的,那么第二次是在什么时候呢?此问题我们先记录一下。
- 继续修改resolveInstanceMethod方法,将其返回一个固定的imp
+ (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];
}
打印结果:
分析:
· 在第一次动态方法决议时,返回sayMaster方法的imp,那么此时说明已经找到一个方法的IMP,直接返回imp。
类方法
针对类方法,与实例方法类似,同样可以通过重写resolveClassMethod类方法来解决前文的崩溃问题,即在LGPerson类中重写该方法,并将sayNB类方法的实现指向类方法lgClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(sayNB)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(lgClassMethod);
return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
打印结果:
分析:
resolveClassMethod类方法的重写需要注意一点,传入的cls不再是类,而是元类,可以通过objc_getMetaClass方法获取类的元类,原因是因为类方法在元类中是实例方法
- 在NSObject中实现resolveInstanceMethod,也可以解决类方法找不到问题
在源码中,如果是元类的话,会有下面这行代码,如果在没有找到类方法,那么就会在元类的对象方法解析方法中查找。
if (!lookUpImpOrNil(inst, sel, cls)) { //如果没有找到或者为空,在元类的对象方法解析方法中查找
resolveInstanceMethod(inst, sel, cls);
}
· 在isa走位图中,继承关系是:元类 -> 根元类 -> NSObject -> nil。元类最终继承于NSObject,那么我们在NSObject中实现resolveInstanceMethod,那么类方法也会最终执行到NSObject中。
NSObject分类
@implementation NSObject (LG)
// 调用方法的时候 - 分类
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@ 来了",NSStringFromSelector(sel));
if (sel == @selector(say666)) {
NSLog(@"%@ 来了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(sayMaster));
Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
else if (sel == @selector(sayNB)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
return NO;
}
@end
分析:
· 打印结果中调用NSObject的resolveInstanceMethod方法,
· 但是也打印一些不相关的信息,这些信息说明了别的方法也会走到此方法中。
总结:
- 实例方法在动态方法决议时执行的是resolveInstanceMethod方法进行补救。
- 类方法在动态方法决议时执行的是resolveClassMethod和resolveInstanceMethod方法进行补救。
消息转发
在慢速查找的流程中,我们了解到,如果快速+慢速没有找到方法实现,动态方法决议也不行,就使用消息转发,但是,我们找遍了源码也没有发现消息转发的相关源码,可以通过以下方式来了解,方法调用崩溃前都走了哪些方法。
流程图:
通过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;
}
-
通过logMessageSend源码,了解到消息发送打印信息存储在/tmp/msgSends 目录,如下所示
-
运行代码,并前往/tmp/msgSends 目录,发现有msgSends开头的日志文件,打开发现在崩溃前,执行了以下方法
· 两次动态方法决议:resolveInstanceMethod方法
· 两次消息快速转发:forwardingTargetForSelector方法
· 两次消息慢速转发:methodSignatureForSelector + resolveInstanceMethod
消息快速转发【第二次机会】
消息的快速转发是执行的forwardingTargetForSelector方法。
-
当慢速查找,以及动态方法决议均没有找到实现时,进行消息转发,首先是进行快速消息转发,即走到forwardingTargetForSelector方法
-
如果返回消息接收者,在消息接收者中还是没有找到,则进入另一个方法的查找流程
-
如果返回nil,则进入慢速消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// runtime + aSelector + addMethod + imp
return [super forwardingTargetForSelector:aSelector];
}
消息慢速转发【第三次机会】
慢速转发
针对第二次机会即快速转发中还是没有找到,则进入最后的一次挽救机会,即在Person中重写methodSignatureForSelector,如下所示
- (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];
}
打印结果:
分析:
- 在methodSignatureForSelector方法中返回了一个方法类型。
- 在forwardInvocation,处理返回的方法类型,并处理invocation事务,修改invocation的target为[LGStudent alloc],调用 [anInvocation invoke] 触发 即LGPerson类的say666实例方法的调用会调用LGStudent的alloc方法。
慢速转发中的动态方法决议
-
我们在resolveInstanceMethod的lookUpImpOrNil处打个断点,查看当前堆栈信息
-
第一次执行动态方法决议时的堆栈是:
_objc_msgSend_uncached ==> lookUpImpOrForward ==> resolveInstanceMethod
-
第二次执行动态方法决议时的堆栈是:
methodSignatureForSelector > lookUpImpOrForward > resolveInstanceMethod
-
在堆栈信息中,第二次执行动态方法决议时,是在执行消息慢速转发时执行的,并且是methodSignatureForSelector之后,forwardInvocation之前。
总结
OC方法调用就是消息发送,那么objc_msgSend流程分别是:
- 方法快速查找:OC底层探索(八)objc_msgSend 流程之方法快速查找
- 方法慢速查找:OC底层探索(九)objc_msgSend 流程之方法慢速查找
- 动态方法决议和消息转发:OC底层探索(十)objc_msgSend 流程之方法的动态方法决议和消息的快速转发、慢速转发(此文章)