笔者整理了一系列有关OC的底层文章,但愿能够帮助到你。html
1.iOS的OC对象建立的alloc原理c++
2.iOS的OC对象的内存对齐缓存
3.iOS的OC的isa的底层原理bash
4.iOS的OC源码分析之类的结构分析app
5.iOS的OC的方法缓存的源码分析ide
6.iOS的OC的方法的查找原理函数
OC的方法的查找是经过消息的发送
来查找函数的IMP
,首先经过objc_msgSend
来进行慢速查找(cache_t
),若是慢速找不到,就须要进行方法的快速查找,具体能够了解iOS的OC的方法的查找原理这篇文章。可是,若是经过慢速和快速的查找都找不到的话,就会直接报错。是否是说若是找不到就没有办法走其余的操做了呢?并非的,接下来这边文章就是介绍方法的决议和消息转发原理。为了接下来的内容介绍定义一个TestObject
类。源码分析
实现下面的代码,其中testErrorMthod
方法是没有在TestObject
类声明和实现的,直接运行是报错的。post
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
函数中,有这段源码,在方法查找不到的时候,会执行到里面去。ui
// 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;
}
复制代码
下面是_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
函数。
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
,下面是在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);
}
复制代码
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
。
若是对不存在的方法的查找,没有实现上面的方法决议
,此时会在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
方法。
在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
方法执行了。可是须要注意的是转发到其余的类执行的方法必需要和被调用的方法相同方法签名的方法(方法名、参数列表、返回值类型都必须一致)。不然的话,仍是报错的。
若是在消息转发的慢速流程中不作处理,此时会执行到消息转发的慢速流程中,须要分别执行两个方法分别是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]
复制代码
OC方法调用是经过objc_msgSend
先经过cache_t
的快速查找,若是找不到就要进行慢速查找
。若是都查找不到方法,就会进入方法的决议
和消息转发流程
。若是查找的类有实现resolveInstanceMethod
或resolveClassMethod
方法对须要查找的方法作处理就完成,不然就进入消息转发
流程。消息转发的流程中先进入消息快速转发流程
,须要实现forwardingTargetForSelector
方法。不然进入消息慢速转发流程
,须要实现methodSignatureForSelector
和forwardInvocation
方法。若是都没有,此时程序只能报错了。最后附上消息转发的流程图