1. 程式人生 > >IOS objc_msgSend執行流程

IOS objc_msgSend執行流程

objc_msgSend大家應該不陌生吧,oc的方法呼叫,其實就是轉換為objc_msgSend的函式呼叫。簡答的可以理解為發訊息,如果 方法呼叫 之後出現了經典的錯誤,unrecognized selector sent to instance... 也可以從以下三個階段進行分析。

objc_msgSend執行流程可以分為三個階段

  • 訊息傳送

    XZdog *dog = [[XZdog alloc]init];
    [dog test];
   //通過生成的 .cpp檔案可以看到底層程式碼的實現
  // objc_msgSend(dog, sel_registerName("test")); 

  • 動態方法解析
@interface XZdog : NSObject
- (void)test;

@end
//方法一
#import "XZdog.h"
#import <objc/runtime.h>

@implementation XZdog

- (void)other{
    NSLog(@"---%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
     struct method_t  *meth=(struct method_t*) class_getInstanceMethod(self, @selector(other));
        NSLog(@"%s %s %p",meth->sel,meth->types,meth->imp);
        class_addMethod(self, sel, meth->imp, meth->types);
        //表示有動態新增方法,這是規範,可以參考官方文件
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


@end



//方法二

@implementation XZdog

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
       Method meth=  class_getInstanceMethod(self, @selector(other));

        class_addMethod(self, sel, method_getImplementation(meth), method_getTypeEncoding(meth));
        //表示有動態新增方法,這是規範,可以參考官方文件
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

//方法三

@implementation XZdog

void c_other(id self ,SEL _cmd){
    NSLog(@"c_other:%@",NSStringFromSelector(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
       
        class_addMethod(self, sel, (IMP)c_other,"
[email protected]
:8"); //表示有動態新增方法,這是規範,可以參考官方文件 return YES; } return [super resolveInstanceMethod:sel]; } @end

我們到runtime原始碼的 objc-runtime-new.mm 類中看下 _class_lookupMethodAndLoadCache3 方法,以下我摘抄了部分原始碼。

retry:    
    runtimeLock.assertReading();

    // 類的快取
//快取中查詢方法 
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

  
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // 父類方法的快取
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    //父類方法的快取列表
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                 
                    break;
                }
            }
            
            // Superclass.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    //訊息傳送者是否是第一次解析 
   //假如沒有實現動態新增 triedResolver = YES 進入retry
   //當triedResolver = YES 時,下次就進不了這個方法,直接進入  imp = (IMP)_objc_msgForward_impcache (訊息轉發)

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        triedResolver = YES;
        goto retry;
    }

    // (訊息轉發)

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

  • 訊息轉發

main.m 呼叫XZBrother *b = [[XZBrother alloc]init];[b test]; 

列印結果:RuntimeTest[1033:83983] -[XZCat test] ,程式碼如下

@interface XZBrother : NSObject
-(void)test;
@end
#import "XZBrother.h"
#import "XZCat.h"

@implementation XZBrother

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if ([email protected](test)) {
        return [[XZCat alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end



@interface XZCat : NSObject
- (void)test;
@end

@implementation XZCat

- (void)test{
    NSLog(@"-----");
    NSLog(@"%s",__func__);
}

@end

由原始碼可知訊息轉發來了這個方法:imp = (IMP)_objc_msgForward_impcache;

全域性搜尋,最後在objc-msg-arm64.s 彙編中發現這個方法的實現

全域性搜尋下_objc_forward_handler 看下這個方法的實現, 你會發現你看的是下面這一塊

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;

然後就想 這都是列印資訊啊,沒錯,runtime的訊息轉發原始碼是不開源的,我們都知道當本類沒有實現方法的時候,訊息轉發機制會呼叫forwardingTargetForSelector方法,假如此時該方法返回的是nil或者沒有實現,又會發生什麼呢?從國外的大神編寫的訊息轉發的虛擬碼中可以看出,他會用methodSignatureForSelector:(SEL)aSelector方法。

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if ([email protected](test)) {
    //返回值為nil 或者 forwardingTargetForSelector不實現的情況
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}
//返回一個方法的簽名:返回值型別/引數型別
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
// 假如這個地方返回的是nil,代表你放棄了這個方法的處理,就會報經典的錯誤了,unrecognized selector sent to instance...

   // return [NSMethodSignature signatureWithObjCTypes:"[email protected]:"];
    return [NSMethodSignature signatureWithObjCTypes:"[email protected]:8"];
}
//NSInvocation封裝了方法的呼叫者,方法名,方法引數
//anInvocation.target 方法的呼叫者
//anInvocation.selector 方法名
//[anInvocation getArgument:NULL atIndex:0] 方法引數
//方法引數atIndex 這裡需要注意下還有兩個隱藏的引數。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[XZCat alloc]init]];
}

訊息轉發之後還是找不到方法的實現就會報最經典的錯誤   unrecognized selector sent to instance...

以上舉例都在物件方法,下面咱們來看看類方法。

@interface XZCat : NSObject
+ (void)eat:(int)cut;
@end

#import "XZCat.h"
@implementation XZCat

+(void)eat:(int)cut{
    NSLog(@"%s",__func__);
}
@end

@interface XZBrother : NSObject
+ (void)eat:(int)cut;
@end

#import "XZBrother.h"
#import "XZCat.h"

@implementation XZBrother
//從老外的原始碼可以看出此方法是訊息轉發者直接呼叫,類方法的reveiver就是類本身。所以
//forwardingTargetForSelector 前面要寫上 加好+ 
//但是這個方法是沒有智慧提示的,沒關係,可以先調出物件方法,手動吧前面的- 改成+ 就OK了。
//同理如果forwardingTargetForSelector返回的是nil時,methodSignatureForSelector 和forwardInvocation 都要吧他改成類方法
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if ([email protected](eat:)) {
    //objc_msgsend([XZBrother class], aSelector)
        return [XZBrother class];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end