_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
。