_objc_msgForward_impcache处理
上篇文章分析了慢速查找流程,如果递归完父类任然没有找到imp,就将imp = forward_imp,因为
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
objc源码全局搜索_objc_msgForward_impcache
汇编中重点看一下
__objc_forward_handler的实现。
全局搜索_objc_forward_handler(注意去掉一个下划线),来到c++代码:
最终(消息转发流程)找不到方法实现,报错并打印报错信息。
找不到方法最后报错流程:
_objc_msgForward_impcache->_objc_forward_handler->_objc_fatal
在慢速查找过程中,如果没有查找到imp,并且没有执行过动态方法决议就执行一次动态方法决议
动态方法决议源码分析
在慢速查找过程lookUpImpOrForward中,执行了一次动态方法决议:
//第一次:0011 & 0010 = 0010 (满足条件,只有一次机会)
//第二次:0000 & 0010 = 0000 (不满足条件)
if (slowpath(behavior & LOOKUP_RESOLVER)) {
//0010 ^ 0010 = 0000 = behavior
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();
// 程序员 你是不是傻 没有这个方法 - imp-nil
// 奔溃 - 友善
// 给你一次机会 拯救地球 -> imp
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// lookUpImpOrForwardTryCache->_lookUpImpTryCache->_lookUpImpTryCache->lookUpImpOrForward
//再次慢速查找一次
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
对象方法决议源码分析
对象方法决议resolveInstanceMethod实现
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
//如果cls中实现了resolveInstanceMethod
SEL resolve_sel = @selector(resolveInstanceMethod:);
///如果cls中没有实现resolveInstanceMethod,系统默认也会有一个实现,所以下面不会return
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//系统会给cls中resolveInstanceMethod发送消息
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 = lookUpImpOrNilTryCache(inst, sel, cls);
省略部分代码
}
对象方法决议:如果cls中实现了resolveInstanceMethod,系统会给resolveInstanceMethod发送消息,如果没有实现,系统也会有个resolveInstanceMethod默认实现。
对象方法决议案例调试
1.准备调试代码:
#import <Foundation/Foundation.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end
@implementation ABPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPerson *p = [ABPerson new];
[p doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
2.执行结果必然报错:
3.按照对象方法决议源码分析,修改
ABPerson实现代码
#import <objc/message.h>
@interface ABPerson : NSObject
-(void)doSomething;
@end
@implementation ABPerson
-(void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(self, @selector(saySomething));
Method method = class_getInstanceMethod(self, @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayImp, type);
}
return [super resolveInstanceMethod:sel];
}
4.执行结果不再报错,并且成功将调用doSomething转为调用saySomething。
类方法决议源码分析
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//如果没有实现resolveClassMethod,return,不过系统默认实现了一个,这里不会return
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
//没有实现元类报错
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
//系统会给nonmeta(元类)中resolveClassMethod发送消息
//nonmeta(元类)中的方法是以对象方法的形式存在的
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(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 = lookUpImpOrNilTryCache(inst, sel, cls);
省略部分代码
}
类方法决议:系统会给nonmeta(元类)中resolveClassMethod发送消息,如果没有实现,会给元类的父类(元类的父类的父类直到NSObject为止)
发消息。系统也会有个resolveClassMethod默认实现。
类方法决议案例调试(一)
1.准备调试代码:
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
+ (void)doSomething;
@end
@implementation ABPeople
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[ABPeople doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
2.执行结果必然报错:
3.按照对象方法决议源码分析,修改ABPeople实现代码
#import <objc/message.h>
@interface ABPeople : NSObject
+ (void)doSomething;
@end
@implementation ABPeople
+ (void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(objc_getMetaClass("ABPeople"), @selector(saySomething));
Method method = class_getInstanceMethod(objc_getMetaClass("ABPeople"), @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("ABPeople"), sel, sayImp, type);
}
return [super resolveClassMethod:sel];
}
@end
4.执行结果不再报错,并且成功将调用doSomething转为调用saySomething。
类方法决议案例调试(二)
我们知道了类方法是在元类中是以实力方法的方式存在的,元类的父类一直往上找是会找到NSObject的,那么NSObject分类也是能够实现类方法决议的,类方法决议代码修改:
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ABPeople : NSObject
//实力方法
- (void)doSomething;
//类方法
+ (void)doSomething;
@end
@implementation ABPeople
@end
@interface NSObject (Cate)
@end
@implementation NSObject (Cate)
-(void)saySomething
{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod--------- :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(doSomething)) {
IMP sayImp = class_getMethodImplementation(self, @selector(saySomething));
Method method = class_getInstanceMethod(self, @selector(saySomething));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, sayImp, type);
}
return NO;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[ABPeople doSomething];
NSLog(@"Hello, World!");
}
return 0;
}
仍然可以成功运行。类别方式的决议同样适用于实例方法
AOP面向切面编程
通过NSObject类别的方式能够全局监听方法找不到,通过 if (sel == @selector(doSomething)) ,可以将特定方法,如doSomething进行后续的处理,如上传日志等。
这样做就能够对原来的代码无侵入,动态的注入代码。但是这样做也会带来相应的性能消耗,如过多的if (sel == @selector(doSomething)) 判断,同时截断了后面的消息转发流程。
所以不建议在动态方法决议阶段做AOP。
instrumentObjcMessageSends写入函数调用日志
instrumentObjcMessageSends源码分析
instrumentObjcMessageSends函数定义:
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
// 如果成立,获得一些方法跟踪
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
//flag赋值给objcMsgLogEnabled
objcMsgLogEnabled = enable;
}
objcMsgLogEnabled在logMessageSend函数中的使用:
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
//写下日志,路径为:"/tmp/msgSends-%d", (int) getpid ()
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;
}
instrumentObjcMessageSends使用
instrumentObjcMessageSends使用很简单,只需要三步:
1.导出函数供外部使用:
extern void instrumentObjcMessageSends(BOOL flag);
2.在需要跟踪的函数上下加上instrumentObjcMessageSends,如:
ABPeople *p = [ABPeople new];
instrumentObjcMessageSends(YES);
[p doSomething];
instrumentObjcMessageSends(NO);
3.文件夹前往路径:/tmp/msgSends
总结
- 无论是对象方法还是类方法,在方法找不到的时候都有一次动态方法决议的机会,需要在类中实现
resolveInstanceMethod(对象方法实现),resolveClassMethod(类方法实现),通过此方法,可以进行后续的处理,如果没有实现将进入消息转发流程。 - 类方法也是按实例方法的形式存在元类中,按照
isa继承链,方法找不到,会一直找到NSObjct,通过NSObjct类别实现对象动态方法决议,可以进行全局监听,进行AOP,但不建议在这个阶段做AOP。