前言
笔者整理了一系列有关OC的底层文章,希望可以帮助到你。
OC的方法的查找是通过消息的发送
来查找函数的IMP
,首先通过objc_msgSend
来进行快速查找(cache_t
),如果快速找不到,就需要进行方法的慢速查找,具体可以了解iOS的OC的方法的查找原理这篇文章。但是,如果通过快速和慢速的查找都找不到的话,就会直接报错。是不是说如果找不到就没有办法走其他的操作了呢?并不是的,接下来这边文章就是介绍方法的决议和消息转发原理。为了接下来的内容介绍定义一个TestObject
类。
1.方法的决议
实现下面的代码,其中testErrorMthod
方法是没有在TestObject
类声明和实现的,直接运行是报错的。
TestObject *testObject = [[TestObject alloc] init];
[testObject performSelector:@selector(testErrorMthod)];
==========运行结果=================
LGTest[1639:40195] -[TestObject testErrorMthod]: unrecognized selector sent to instance 0x1010c3600
LGTest[1639:40195] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestObject testErrorMthod]: unrecognized selector sent to instance 0x1010c3600'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3c7438ab __exceptionPreprocess + 250
1 libobjc.A.dylib 0x000000010038dfea objc_exception_throw + 42
2 CoreFoundation 0x00007fff3c7c2b61 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff3c6a7adf ___forwarding___ + 1427
4 CoreFoundation 0x00007fff3c6a74b8 _CF_forwarding_prep_0 + 120
5 libobjc.A.dylib 0x00000001003ccf26 -[NSObject performSelector:] + 70
6 LGTest 0x0000000100001afd main + 93
7 libdyld.dylib 0x00007fff73d6b7fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
在之前介绍方法的慢速查找流程中的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;
}
1.1 _class_resolveMethod
下面是_class_resolveMethod
的源码,其中cls
是类,sel
方法的编号,inst
是实例对象。
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
判断是否是元类
,通过之前的文章可以知道类方法
是存在元类
中的。所以如果传进来的cls
是类,就直接执行_class_resolveInstanceMethod
函数,如果是元类
执行_class_resolveClassMethod
函数。
1.2 _class_resolveInstanceMethod
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(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 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));
}
}
}
其中lookUpImpOrNil
函数对类方法SEL_resolveInstanceMethod
查找是否有存在,发现SEL_resolveInstanceMethod
的实现是resolveInstanceMethod
方法,这个是在NSObject
类里面有实现的,所以这个是返回true
的。
然后接着执行,下面这段代码通过objc_msgSend
查找当前的类是否执行resolveInstanceMethod
方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
通过前面的文章,可以知道如果当前的类没有实现resolveInstanceMethod
类方法就会查找父类是否实现,因为在NSObject
类中是默认实现返回NO
的,如果当前的类有实现resolveInstanceMethod
类方法就会执行方法里的内容。并且在实现的resolveInstanceMethod
类方法中对没有找到的sel
重新赋值一个IMP
,并且会将实现的IMP
缓存起来,通过lookUpImpOrNil
来重新查找一次,下面是在TestObject
类的实现代码。
-(void)testOk{
NSLog(@"%p===testOk",__func__);
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"===执行resolveInstanceMethod===%s===%@",__func__,NSStringFromSelector(sel));
if(sel == @selector(testErrorMthod)){
Method okMethod = class_getInstanceMethod(self, @selector(testOk));
IMP okImp = method_getImplementation(okMethod);
const char *type = method_getTypeEncoding(okMethod);
return class_addMethod(self, sel, okImp, type);
}
return [super resolveInstanceMethod:sel];
}
======执行的结果=======
LGTest[2769:92089] ===执行resolveInstanceMethod===+[TestObject resolveInstanceMethod:]===testErrorMthod
LGTest[2769:92089] 0x100001f2a===testOk
Program ended with exit code: 0
此时还执行一次lookUpImpOrNil
函数在缓存中查找。所以在TestObject
类中定义了这个方法并且为testErrorMthod
赋值了新的IMP
。
// 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(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
上面的是对实例方法的动态决议,其实对类方法的也是差不多的,如果是类方法此时会执行如下源码
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
1.3 _class_resolveClassMethod
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));
}
}
}
通过源码可以知道是与_class_resolveInstanceMethod
源码是差不多的,主要区别是,这个源码的SEL_resolveClassMethod
是要实现resolveClassMethod
这个类方法,需要在TestObject
类中实现类方法resolveClassMethod
并在这个方法里面实现逻辑。但是为什么执行完_class_resolveClassMethod
函数之后还会再做一次lookUpImpOrNil
函数的判断呢?因为如果在_class_resolveClassMethod
是没有做处理的,由于元类
的查找方法查找流程是会往根元类
查找最终会找到NSObject
这个类,所以如果在根元类
都找不到的情况下会找到NSObject
类的方法里面。而NSObject
类是有默认实现了这两个类方法的并且默认返回NO
。
2. 消息转发
如果对不存在的方法的查找,没有实现上面的方法决议
,此时会在lookUpImpOrForward
函数中执行
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
其中_objc_msgForward_impcache
就会执行到汇编中的内容,然后执行__objc_msgForward
,最终会执行到_objc_forward_handler
函数中,最终是会报错的。
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
// Default forward handler halts the process.
__attribute__((noreturn)) 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
函数中有一个可以打印log
的方法log_and_fill_cache
中的logMessageSend
方法里面有介绍可以根据objcMsgLogEnabled
属性来控制打印log
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);
objcMsgLogEnabled = enable;
}
为了可以看到消息转发的过程中实现了那些方法,可以在mac的项目中实现
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *test = [TestObject alloc] ;
instrumentObjcMessageSends(true);
[test performSelector:@selector(testErrorMthod)];
instrumentObjcMessageSends(false);
}
return 0;
}
就可以在路径:/tmp/msgSends-
找到生成的msgSends
文件
这个msgSends-7265
文件的内容
这个打印log是要在mac的项目下才可以,如果在其他的项目下是会报objc[6984]: lock 0x100cbf0c0 (runtimeLock) acquired before 0x100cbf040 (objcMsgLogLock) with no defined lock order这种错误。
从打印出来的方法可以知道,在消息的转发的过程中执行的过程是resolveInstanceMethod
-->forwardingTargetForSelector
-->methodSignatureForSelector
-->resolveInstanceMethod
-->doesNotRecognizeSelector
。所以如果不执行resolveInstanceMethod
方法决议,会执行forwardingTargetForSelector
方法。
2.1 消息快速转发
在objc
的源码中可以找到在NSObject.mm
文件中有定义和实现forwardingTargetForSelector
的实例方法和类方法。
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
在官方的文档中有对forwardingTargetForSelector
方法的介绍
大概的意思是,forwardingTargetForSelector
主要是返回一个不是自身(如果是self会进入死循坏)的对象去处理sel
这个当前类无法处理的消息,其他的情况可以调用super
方法。如果处理不了,会转到效率低下的forwardInvocation
。在效率方面,forwardingTargetForSelector
领先forwardInvocation
一个数量级,因此,如果可以的话最好避免使用后者来做消息转发。下面在TestObject
类中添加多一个TestForwardObject
类,并且在TestObject
类中实现forwardingTargetForSelector
方法。
@interface TestForwardObject : NSObject
@end
@implementation TestForwardObject
-(void)testErrorMthod{
NSLog(@"TestForwardObject的testErrorMthod方法%p",__func__);
}
@end
//在TestObject类中实现的方法
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"方法名字:%@",NSStringFromSelector(aSelector));
if(aSelector == @selector(testErrorMthod)){
return [TestForwardObject alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
//=========运行的结果========
LGTest[1308:29082] 方法名字:testErrorMthod
LGTest[1308:29082] TestForwardObject的testErrorMthod方法0x100001eb4
从中可以看到消息的转发到TestForwardObject
的testErrorMthod
方法执行了。但是需要注意的是转发到其他的类执行的方法必须要和被调用的方法相同方法签名的方法(方法名、参数列表、返回值类型都必须一致)。否则的话,还是报错的。
2.2消息慢速转发
如果在消息转发的慢速流程中不做处理,此时会执行到消息转发的慢速流程中,需要分别执行两个方法分别是methodSignatureForSelector
和forwardInvocation
。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"方法名字:%@",NSStringFromSelector(aSelector));
if(aSelector == @selector(testErrorMthod)){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"执行forwardInvocation:%s",__func__);
}
//======运行结果==========
LGTest[1222:21266] 方法名字:testErrorMthod
LGTest[1222:21266] 执行forwardInvocation:-[TestObject forwardInvocation:]
在这个流程中methodSignatureForSelector
是返回的方法的签名,可以参考
苹果官方类型编码。可以发现在forwardInvocation
方法中就算不做处理也不会奔溃,因为每个方法其实就是一个事务,不做处理就会失效,在forwardInvocation
中做处理的话,可以如下:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"执行forwardInvocation:%s",__func__);
SEL inVocationSeletor = [anInvocation selector];
if([[TestForwardObject alloc] respondsToSelector:inVocationSeletor]){
[anInvocation invokeWithTarget:[TestForwardObject alloc]];
}else{
[super forwardInvocation:anInvocation];
}
}
//=====运行结果===========
LGTest[1465:30634] 方法名字:testErrorMthod
LGTest[1465:30634] 执行forwardInvocation:-[TestObject forwardInvocation:]
LGTest[1465:30634] TestForwardObject的testErrorMthod方法-[TestForwardObject testErrorMthod]
3.最后
OC方法调用是通过objc_msgSend
先通过cache_t
的快速查找,如果找不到就要进行慢速查找
。如果都查找不到方法,就会进入方法的决议
和消息转发流程
。如果查找的类有实现resolveInstanceMethod
或resolveClassMethod
方法对需要查找的方法做处理就完成,否则就进入消息转发
流程。消息转发的流程中先进入消息快速转发流程
,需要实现forwardingTargetForSelector
方法。否则进入消息慢速转发流程
,需要实现methodSignatureForSelector
和forwardInvocation
方法。如果都没有,此时程序只能报错了。最后附上消息转发的流程图