Runtime消息傳遞和消息轉(zhuǎn)發(fā)|你一定看得懂

Runtime簡介
==== Runtime的特性主要是消息(方法)傳遞機制,當向?qū)ο蟀l(fā)送一條消息時,如果能在對象的類或所繼承類中的方法列表中找到對應的SEL,即進行方法調(diào)用,否則進行消息轉(zhuǎn)發(fā)。底層主要用C和匯編代碼編寫,使得動態(tài)系統(tǒng)變得高效。

消息傳遞

關鍵函數(shù):
id objc_msgSend(id self, SEL op, ...);
self : 指向接收消息的類的實例的指針地址,可以理解成調(diào)用方法的對象
op  : 處理消息的方法選擇器 
(SEL:類成員(方法)的指針,不同于C中的函數(shù)指針直接保存了方法的地址,
在這只是 方法的編號)

首先介紹下消息傳遞時一個非常重要的家伙,指向 類的數(shù)據(jù)結(jié)構(gòu)體 的指針 isa :
它是一個class類型的指針,實例對象的isa指向它的類對象,類對象的isa指向元類對象(meta class),元類對象isa指向根元類(root class),根元類isa指向自身。

screenshot.png

/* isa結(jié)構(gòu)體:這其中包含了指向其父類的isa指針
及Dispatch table(key為SEL value為IMP)
其中結(jié)構(gòu)體 objc_method_list 中包含結(jié)構(gòu)體成員 objc_method,
objc_method中 包含成員 SEL和IMP(可在runtime.h中查找)*/

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

例如對象調(diào)用方法 [obj callAndy],編譯器轉(zhuǎn)成消息發(fā)送時是這樣 objc_msgSend(obj, callAndy),Runtime時執(zhí)行的流程是:

1 .通過obj的isa指針找到它的數(shù)據(jù)結(jié)構(gòu)體 ;
2 .根據(jù)方法名callAndy生成的SEL(方法編號)查詢緩存 objc_cache,若命中則直接調(diào)用,否則繼續(xù)下一步
3 .在 class 的 Dispatch table(調(diào)度表)中查找對應的SEL ; 若class 中沒找到,繼續(xù)往它的 superclass 中找 ;
一旦找到就去執(zhí)行它的IMP(函數(shù)指針,方法實現(xiàn)的地址) 。
否則就進行消息轉(zhuǎn)發(fā)。
注:objc_cache:查找結(jié)束時為了高效處理,objc_cache會把已被調(diào)用的函數(shù)緩存下來,提高函數(shù)查詢的效率。 --找到callAndy 之后,把callAndy 的SEL 作為key ,IMP作為value 給存起來。當再次收到callAndy 消息的時候,可以直接在objc_cache 里找到,避免再次遍歷。

如果你剛才去找Andy,問了他dad,問了他dad的dad,又問了他dad的dad的dad...,還是找不到人,這個時候他可能鬼混去了,但是鬼混歸鬼混,他還不叫你,哼,也別顧及兄弟情義了,去報警吧,報人口失蹤,轉(zhuǎn)交給警察叔叔處理...
哈哈,扯淡歸扯淡,咱言歸正傳

消息轉(zhuǎn)發(fā)
獻上流程圖

轉(zhuǎn)發(fā)一覽.jpeg

大家看它們很不害臊的給自己標紅了,就知道其中有四個關鍵方法了:

1. + (BOOL)resolveInstanceMethod:(SEL)sel  
   + (BOOL)resolveClassMethod:(SEL)sel
  實例方法和類方法分別調(diào)用這兩個,返回NO才繼續(xù)執(zhí)行轉(zhuǎn)發(fā)流程 執(zhí)行方法2
在此方法中若動態(tài)添加方法給接收者,則返回YES,并且執(zhí)行所添加的方法
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically))
    {      
          /*  v@: v表示返回值為void類型 
固定的兩個參數(shù): @表示第一個參數(shù)類型是id也就是當前對象self
                   :表示第二個參數(shù)類型是SEL即@selector(...)
          */
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSel];
}
2. - (id)forwardingTargetForSelector:(SEL)aSelector
//在此可以返回一個已知對象,此對象實現(xiàn)了未知消息,使得消息可以轉(zhuǎn)發(fā)給這個對象
    若在此返回轉(zhuǎn)發(fā)目標,則執(zhí)行轉(zhuǎn)發(fā)目標的方法,結(jié)束轉(zhuǎn)發(fā)流程;
    若返回nil ,執(zhí)行方法3
3. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 
返回nil則會被標記為消息無法處理 系統(tǒng)則會crash :unrecognized selector...
返回方法簽名則執(zhí)行方法4 ;
4. - (void)forwardInvocation:(NSInvocation *)anInvocation
為了響應對象本身不認識的方法,必須覆蓋 methodSignatureForSelector: 
以及 forwardInvocation 方法
(詳細內(nèi)容可參閱開發(fā)者文檔 Xcode -> Window -> Developer Documentation)

創(chuàng)建一個NSNull分類,避免當對NSNull對象發(fā)送一個ClassArray所包含類中實例 存在的消息時,應用直接Crash,而是打印錯誤調(diào)用的堆棧信息,在保證應用繼續(xù)運行的情況下幫助開發(fā)者更好的定位代碼的問題。當然這只是做編碼時千慮一失最后的一塊盾牌,更重要的還是要讓自己做好千慮這件事。(此處要注意nil和null的區(qū)別,nil是不占內(nèi)存的,向它發(fā)送消息不會crash,而null占有內(nèi)存是會進行消息傳遞的,所以會crash,這個值一般由服務器下發(fā)的情況較多)
(分類創(chuàng)建:新建Objective-C File,F(xiàn)ile Type:Category,Class:NSNull)

#define ClassArray @[[NSMutableArray class],[NSMutableDictionary class],[NSMutableString class],[NSNumber class],[NSDate class],[NSData class]]

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature *signature;
    if (!signature)
    {
        for (Class someClass in ClassArray)
        {
            @try {
                //若某個類(它的實例)能夠響應selector 設置相應的方法簽名
                if ([someClass instancesRespondToSelector:selector])
                {
                    signature = [someClass instanceMethodSignatureForSelector:selector];
                    break;
                }
            } @catch (NSException *exception) {}
        }
    }
    if (signature){
        return signature;
    }
    return [[self superclass] methodSignatureForSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = nil;
    [invocation invoke];
    
    //獲取方法調(diào)用堆棧信息 彈窗提示
    NSArray *stack = [NSThread callStackSymbols];
    NSString *infoStr = [NSString stringWithFormat:@"%@ .. %@ -- stackInfo: %@",NSStringFromClass([invocation.target class]),NSStringFromSelector(invocation.selector),stack];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"請聯(lián)系開發(fā)人員反映問題!" message:infoStr preferredStyle:UIAlertControllerStyleAlert];
    UIViewController *vCtrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"復制錯誤信息" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
        pasteboard.string = infoStr;
    }];
    [alert addAction:action];
    if (vCtrl){
        [vCtrl presentViewController:alert animated:YES completion:nil];
    }
}

學習多篇博客以及實踐之后作此記錄,不準確的地方希望能得到各位Coder指教,感謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,327評論 0 7
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,248評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 828評論 0 2
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 883評論 0 1
  • 本文詳細整理了 Cocoa 的 Runtime 系統(tǒng)的知識,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 865評論 0 4

友情鏈接更多精彩內(nèi)容