iOS的OC的方法的决议与消息转发原理

前言

笔者整理了一系列有关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类。源码分析

1.方法的决议

实现下面的代码,其中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;
    }
复制代码

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,下面是在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

复制代码

从中能够看到消息的转发到TestForwardObjecttestErrorMthod方法执行了。可是须要注意的是转发到其余的类执行的方法必需要和被调用的方法相同方法签名的方法(方法名、参数列表、返回值类型都必须一致)。不然的话,仍是报错的。

2.2消息慢速转发

若是在消息转发的慢速流程中不作处理,此时会执行到消息转发的慢速流程中,须要分别执行两个方法分别是methodSignatureForSelectorforwardInvocation

-(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的快速查找,若是找不到就要进行慢速查找。若是都查找不到方法,就会进入方法的决议消息转发流程。若是查找的类有实现resolveInstanceMethodresolveClassMethod方法对须要查找的方法作处理就完成,不然就进入消息转发流程。消息转发的流程中先进入消息快速转发流程,须要实现forwardingTargetForSelector方法。不然进入消息慢速转发流程,须要实现methodSignatureForSelectorforwardInvocation方法。若是都没有,此时程序只能报错了。最后附上消息转发的流程图

相关文章
相关标签/搜索