runtime的消息查找階段已經探索完了,當都不知足條件,會進入消息轉發階段app
runtime的消息轉發分爲3步:post
根據上篇文章,咱們看了lookUpImpOrForward
這個方法,在最後都不知足的狀況下,會調用resolveMethod_locked
這個方法ui
resolveMethod_locked
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
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 (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
複製代碼
cls若是不是元類,說明當前是對象方法,調用resolveInstanceMethod
spa
cls若是是元類,說明當前是類方法,調用resolveClassMethod
。走完若是沒找到,還會走一遍resolveInstanceMethod
方法3d
lookUpImpOrForward
方法,返回imp
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
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 = lookUpImpOrNil(inst, sel, cls);
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));
}
}
}
複製代碼
容錯判斷if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA()))
,是爲了看當前類以及父類、元類等有沒有實現resolveInstanceMethod:
,若是都沒有就不必繼續往下走了。(NSObject裏面是默認有這個方法的)日誌
若是實現了,就經過objc_msgSend
,向當前cls
發送消息,也就是調用resolveInstanceMethod:
這個已經實現的方法,在這個方法裏,咱們已經手動給sel
添加了一個imp
code
而後再經過lookUpImpOrNil
檢查一遍,拿到咱們添加的imp
orm
最後返回到lookUpImpOrForward
方法,從新循環去找,最終返回imp
對象
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(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);
}
}
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 = lookUpImpOrNil(inst, sel, cls);
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));
}
}
}
複製代碼
和上面的容錯同樣
若是實現了,就經過objc_msgSend
,向當前cls的元類 ->
發送消息,調用resolveClassMethod
方法,給sel
添加imp
,跟上面流程同樣
特別提醒:若是沒有實現,還會走一次 對象方法解析 resolveInstanceMethod
,是由於有一種特殊狀況,就是元類的父類是根元類,根元類的父類是NSObject,因此要在走一次這個方法,看NSObject裏面有沒有實現。
NSObject
裏面的 resolveInstanceMethod
方法,但並不意味着 全部的防崩潰方法都要寫在這裏!緣由:+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(sel);
if ([name isEqual:@"sayHello"]) {
//若是是調用sayHello方法,就返回一個sayHappy的IMP
IMP newIMP = class_getMethodImplementation(self, @selector(sayHappy));
Method newMethod = class_getInstanceMethod(self, @selector(sayHappy));
const char *newType = method_getTypeEncoding(newMethod);
return class_addMethod(self, sel, newIMP, newType);
}
return [super resolveInstanceMethod:sel];
}
複製代碼
若是咱們繼續上面的思路走的話,會發現這個方法已經走完了,線索就斷掉了。。。
其實在lookUpImpOrForward
裏有一個log_and_fill_cache
方法,這個方法裏面有一個logMessageSend
方法調用, 這一系列操做(方法就不一一展現了,太多了,直接到達目的地)中有一步驟是打印日誌,而日誌存儲的位置:
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid **());**
複製代碼
經過全局探索,發現是否打印的開關是這麼一句代碼:
instrumentObjcMessageSends(BOOL flag)
複製代碼
因而,咱們跑一下代碼看一下,(隨便建立一份工程,源碼跑不動)準備工做:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *objc = [[LGPerson alloc] init];
//打開開關
instrumentObjcMessageSends(true);
//這個實例方法,只聲明瞭,沒有實現,跑起來確定會崩潰
[objc sayHello];
//關閉開關
instrumentObjcMessageSends(false);
}
return 0;
}
複製代碼
咱們運行,找一下/tmp/msgSends
這個路徑存儲的日誌文件看一下:
從日誌中能夠看出,動態方法解析以後調用了forwardingTargetForSelector
方法。咱們去官方看一下這段代碼的含義:
大體意思就是,找一個備用的接收者,即返回一個實現了這個方法的對象。
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(aSelector);
if ([name isEqual:@"sayHello"]) {
//Jack這個類 聲明並實現了 sayHello這個方法
return [Jack alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
若是沒有這個備用接收者,或者沒有實現這個方法,就會走到慢速轉發
在快速轉發的官方介紹中,我有圈起來,若是不實現快速轉發,會調用forwardInvocation:
在這方法,那咱們來看一下這個方法的官方介紹:
官方介紹中也圈起來了,要重寫forwardInvocation:
這個方法,就必須同時重寫methodSignatureForSelector:
方法,咱們繼續看一下官方介紹:
慢速流程流程就是先走methodSignatureForSelector
提供一個方法簽名,而後走forwardInvocation
經過NSInvocation
來實現消息的轉發
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(aSelector);
if ([name isEqual:@"sayHello"]) {
//方法簽名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//其實這個方法能夠空着不寫
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[Jack alloc] respondsToSelector:aSelector]){
[anInvocation invokeWithTarget:[Jack alloc]];
}else{
[super forwardInvocation:anInvocation];
}
}
複製代碼
有這麼一個狀況,resolveInstanceMethod
調用了2次
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s ",__func__);
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
return [super forwardingTargetForSelector:aSelector];
}
//先拿到方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s ",__func__);
NSString *name = NSStringFromSelector(aSelector);
if ([name isEqual:@"sayHello"]) {
//方法簽名 v-返回值,@-傳入對象,:-傳入sel
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//經過anInvocation進行消息轉發,也能夠空着這個方法不做處理
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
}
複製代碼
緣由: 在調用methodSignatureForSelector
方法的時候,咱們傳了一個type,這時候系統會去進行匹配,走到class_getInstanceMethod
這個方法裏面:
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
複製代碼
又會走到lookUpImpOrForward
方法,最終仍是進入resolveMethod_locked
這個方法,而後就會再走一次 resolveInstanceMethod
方法。
** 坑點總結**:若是出現走兩次resolveInstanceMethod
方法的狀況,是由於在簽名以後,系統作了一些操做:會根據咱們傳入的簽名type進行匹配,調用class_getInstanceMethod
方法就會再走一次。
前提:整個查找流程沒有找到該方法,而後進入消息轉發流程
首先來到動態方法解析,添加一個方法實現(給sel
指定一個imp
)
+(BOOL)resolveInstanceMethod:(SEL)sel
方法+(BOOL)resolveClassMethod:(SEL)sel
方法快速轉發,若是動態方法解析沒有找到對應的處理方法,就會來到這裏
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,return一個備用接收者慢速轉發,若是快速轉發也沒有處理,就會來到這裏
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法拿到方法簽名-(void)forwardInvocation:(NSInvocation *)anInvocation
經過NSInvocation
來實現消息轉發