objc_msgSend做了什么?

objc_msgSend 是 OC 編譯成 C 語(yǔ)言過(guò)程時(shí)調(diào)用的,也是調(diào)用最頻繁的一個(gè)語(yǔ)句画饥。這里我們分析下,蘋(píng)果是怎么調(diào)用 objc_msgSend 以及調(diào)用之后的操作是怎樣的浊猾。

這里 我們可以下載到蘋(píng)果公開(kāi)的部分源碼荒澡。

1.objc_msgSend 的調(diào)用

objc_msgSend是用匯編語(yǔ)言實(shí)現(xiàn)的,所以針對(duì)不同的硬件環(huán)境采用了不同的文件与殃,這里我們只分析arm環(huán)境下的源碼单山。

/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in r12
 * Forwarding returned in Z flag
 * r9 reserved for our use but not used
 *
 ********************************************************************/
    ENTRY _objc_msgSend
    
    cbz r0, LNilReceiver_f      //判斷Receiver是否存在

    ldr r9, [r0]        // r9 = self->isa
    GetClassFromIsa         // r9 = class  通過(guò)isa指針回去類(lèi)對(duì)象
    CacheLookup NORMAL    //cache內(nèi)查找IMP是否存在
    // cache hit, IMP in r12, eq already set for nonstret forwarding
    bx  r12         // call imp

    CacheLookup2 NORMAL
    // cache miss
    ldr r9, [r0]        // r9 = self->isa
    GetClassFromIsa         // r9 = class
    b   __objc_msgSend_uncached   //調(diào)用_objc_msgSend_uncached
LNilReceiver:
    // r0 is already zero
    mov r1, #0
    mov r2, #0
    mov r3, #0
    FP_RETURN_ZERO
    bx  lr  

    END_ENTRY _objc_msgSend


/////////////////////////////////////////////////////////////////////
//
// CacheLookup  NORMAL|STRET
// CacheLookup2 NORMAL|STRET
//
// Locate the implementation for a selector in a class's method cache.
//
// Takes: 
//    $0 = NORMAL, STRET
//    r0 or r1 (STRET) = receiver
//    r1 or r2 (STRET) = selector
//    r9 = class to search in
//
// On exit: r9 clobbered
//      (found) continues after CacheLookup, IMP in r12, eq set
//      (not found) continues after CacheLookup2
//
/////////////////////////////////////////////////////////////////////
    
.macro CacheLookup
    
    ldrh    r12, [r9, #CACHE_MASK]  // r12 = mask
    ldr r9, [r9, #CACHE]    // r9 = buckets
.if $0 == STRET
    and r12, r12, r2        // r12 = index = SEL & mask
.else
    and r12, r12, r1        // r12 = index = SEL & mask
.endif
    add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
    ldr r12, [r9, #CACHED_SEL]  // r12 = bucket->sel
6:
.if $0 == STRET
    teq r12, r2
.else
    teq r12, r1
.endif
    bne 8f
    ldr r12, [r9, #CACHED_IMP]  // r12 = bucket->imp

.if $0 == STRET
    tst r12, r12        // set ne for stret forwarding
.else
    // eq already set for nonstret forwarding by `teq` above
.endif

.endmacro

.macro CacheLookup2
#if CACHED_SEL != 0
#   error this code requires that SEL be at offset 0
#endif
8:  
    cmp r12, #1
    blo 8f          // if (bucket->sel == 0) cache miss
    it  eq          // if (bucket->sel == 1) cache wrap
    ldreq   r9, [r9, #CACHED_IMP]   // bucket->imp is before first bucket
    ldr r12, [r9, #8]!      // r12 = (++bucket)->sel
    b   6b
8:

.endmacro
/////////////////////////////////////////////////////////////////////
//
// GetClassFromIsa  return-type
//
// Given an Isa, return the class for the Isa.
//
// Takes:
//    r9 = class
//
// On exit: r12 clobbered
//          r9 contains the class for this Isa.
//
/////////////////////////////////////////////////////////////////////
.macro GetClassFromIsa

#if SUPPORT_INDEXED_ISA
    // Note: We are doing a little wasted work here to load values we might not
    // need.  Branching turns out to be even worse when performance was measured.
    MI_GET_ADDRESS(r12, _objc_indexed_classes)
    tst.w   r9, #ISA_INDEX_IS_NPI_MASK
    itt ne
    ubfxne  r9, r9, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS
    ldrne.w r9, [r12, r9, lsl #2]
#endif

.endmacro


/********************************************************************
 * IMP cache_getImp(Class cls, SEL sel)
 *
 * On entry:    r0 = class whose cache is to be searched
 *              r1 = selector to search for
 *
 * If found, returns method implementation.
 * If not found, returns NULL.
 ********************************************************************/

    STATIC_ENTRY _cache_getImp

    mov r9, r0
    CacheLookup NORMAL
    // cache hit, IMP in r12
    mov r0, r12
    bx  lr          // return imp
    
    CacheLookup2 GETIMP
    // cache miss, return nil
    mov r0, #0
    bx  lr

    END_ENTRY _cache_getImp

    STATIC_ENTRY __objc_msgSend_uncached

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band r9 is the class to search
    
    MethodTableLookup NORMAL    // returns IMP in r12 調(diào)用MethodTableLookup
    bx  r12

    END_ENTRY __objc_msgSend_uncached

/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup    NORMAL|STRET
//
// Locate the implementation for a selector in a class's method lists.
//
// Takes: 
//    $0 = NORMAL, STRET
//    r0 or r1 (STRET) = receiver
//    r1 or r2 (STRET) = selector
//    r9 = class to search in
//
// On exit: IMP in r12, eq/ne set for forwarding
//
/////////////////////////////////////////////////////////////////////
    
.macro MethodTableLookup
    
    stmfd   sp!, {r0-r3,r7,lr}
    add r7, sp, #16
    sub sp, #8          // align stack
    FP_SAVE

.if $0 == NORMAL
    // receiver already in r0
    // selector already in r1
.else
    mov     r0, r1          // receiver
    mov     r1, r2          // selector
.endif
    mov r2, r9          // class to search

    blx __class_lookupMethodAndLoadCache3
    mov r12, r0         // r12 = IMP
    
.if $0 == NORMAL
    cmp r12, r12        // set eq for nonstret forwarding
.else
    tst r12, r12        // set ne for stret forwarding
.endif

    FP_RESTORE
    add sp, #8          // align stack
    ldmfd   sp!, {r0-r3,r7,lr}

.endmacro

以上匯編大家看起來(lái)費(fèi)勁。這里轉(zhuǎn)成 C 語(yǔ)言的偽代碼:


/*
objc_msgSend的C語(yǔ)言版本偽代碼實(shí)現(xiàn).
receiver: 是調(diào)用方法的對(duì)象
op: 是要調(diào)用的方法名稱字符串
*/
id  objc_msgSend(id receiver, SEL op, ...)
{

    //1............................ 對(duì)象空值判斷幅疼。
    //如果傳入的對(duì)象是nil則直接返回nil
    if (receiver == nil)
        return nil;
    
   //2............................ 獲取或者構(gòu)造對(duì)象的isa數(shù)據(jù)米奸。
    void *isa = NULL;
    //如果對(duì)象的地址最高位為0則表明是普通的OC對(duì)象,否則就是Tagged Pointer類(lèi)型的對(duì)象
    if ((receiver & 0x8000000000000000) == 0) {
        struct objc_object  *ocobj = (struct objc_object*) receiver;
        isa = ocobj->isa;
    }
    else { //Tagged Pointer類(lèi)型的對(duì)象中沒(méi)有直接保存isa數(shù)據(jù)爽篷,所以需要特殊處理來(lái)查找對(duì)應(yīng)的isa數(shù)據(jù)悴晰。
        
        //如果對(duì)象地址的最高4位為0xF, 那么表示是一個(gè)用戶自定義擴(kuò)展的Tagged Pointer類(lèi)型對(duì)象
        if (((NSUInteger) receiver) >= 0xf000000000000000) {
            
            //自定義擴(kuò)展的Tagged Pointer類(lèi)型對(duì)象中的52-59位保存的是一個(gè)全局?jǐn)U展Tagged Pointer類(lèi)數(shù)組的索引值。
            int  classidx = (receiver & 0xFF0000000000000) >> 52
            isa =  objc_debug_taggedpointer_ext_classes[classidx];
        }
        else {
            
            //系統(tǒng)自帶的Tagged Pointer類(lèi)型對(duì)象中的60-63位保存的是一個(gè)全局Tagged Pointer類(lèi)數(shù)組的索引值逐工。
            int classidx = ((NSUInteger) receiver) >> 60;
            isa  =  objc_debug_taggedpointer_classes[classidx];
        }
    }
    
   //因?yàn)閮?nèi)存地址對(duì)齊的原因和虛擬內(nèi)存空間的約束原因铡溪,
   //以及isa定義的原因需要將isa與上0xffffffff8才能得到對(duì)象所屬的Class對(duì)象。
    struct objc_class  *cls = (struct objc_class *)(isa & 0xffffffff8);
    
   //3............................ 遍歷緩存哈希桶并查找緩存中的方法實(shí)現(xiàn)泪喊。
    IMP  imp = NULL;
    //cmd與cache中的mask進(jìn)行與計(jì)算得到哈希桶中的索引棕硫,來(lái)查找方法是否已經(jīng)放入緩存cache哈希桶中。
    int index =  cls->cache.mask & op;
    while (true) {
        
        //如果緩存哈希桶中命中了對(duì)應(yīng)的方法實(shí)現(xiàn)袒啼,則保存到imp中并退出循環(huán)哈扮。
        if (cls->cache.buckets[index].key == op) {
              imp = cls->cache.buckets[index].imp;
              break;
        }
        
        //方法實(shí)現(xiàn)并沒(méi)有被緩存,并且對(duì)應(yīng)的桶的數(shù)據(jù)是空的就退出循環(huán)
        if (cls->cache.buckets[index].key == NULL) {
             break;
        }
        
        //如果哈希桶中對(duì)應(yīng)的項(xiàng)已經(jīng)被占用但是又不是要執(zhí)行的方法蚓再,則通過(guò)開(kāi)地址法來(lái)繼續(xù)尋找緩存該方法的桶滑肉。
        if (index == 0) {
            index = cls->cache.mask;  //從尾部尋找
        }
        else {
            index--;   //索引減1繼續(xù)尋找。
        }
    } /*end while*/

   //4............................ 執(zhí)行方法實(shí)現(xiàn)或方法未命中緩存處理函數(shù)
    if (imp != NULL)
         return imp(receiver, op,  ...); //這里的... 是指?jìng)鬟f給objc_msgSend的OC方法中的參數(shù)摘仅。
    else
         return objc_msgSend_uncached(receiver, op, cls, ...);
}

/*
  方法未命中緩存處理函數(shù):objc_msgSend_uncached的C語(yǔ)言版本偽代碼實(shí)現(xiàn)靶庙,這個(gè)函數(shù)也是用匯編語(yǔ)言編寫(xiě)。
*/
id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls)
{
   //這個(gè)函數(shù)很簡(jiǎn)單就是直接調(diào)用了_class_lookupMethodAndLoadCache3 來(lái)查找方法并緩存到struct objc_class中的cache中娃属,最后再返回IMP類(lèi)型六荒。
  IMP  imp =   _class_lookupMethodAndLoadCache3(receiver, op, cls);
  return imp(receiver, op, ....);
}

通過(guò)以上分析。我們發(fā)現(xiàn)還要調(diào)用 __class_lookupMethodAndLoadCache3 方法膳犹。
繼續(xù)分析__class_lookupMethodAndLoadCache3內(nèi)做了些什么操作恬吕。
最總我們發(fā)現(xiàn)這個(gè)方法是在 objc-runtime.mm 內(nèi)實(shí)現(xiàn)的。
實(shí)現(xiàn)代碼如下:

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel); //1........ cache查找 匯編實(shí)現(xiàn)
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();//加鎖
    checkIsKnownClass(cls);//校驗(yàn)是否存在這個(gè)類(lèi)

    if (!cls->isRealized()) { //2........類(lèi)對(duì)象是否初始化
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.

    imp = cache_getImp(cls, sel); //3.1........cache內(nèi)是否查到须床?查到返回IMP
    if (imp) goto done;

    // Try this class's method lists.     3.2........... 類(lèi)對(duì)象內(nèi)查找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists. 3.3 ............父類(lèi)對(duì)象cache和method list內(nèi)查找
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
// 4........動(dòng)態(tài)加載  做標(biāo)記 只加載一次
    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;      //重新加載一遍
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

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

 done:
    runtimeLock.unlock();

    return imp;
}


通過(guò)以上分析 發(fā)現(xiàn)一個(gè)問(wèn)題铐料,這里只有 resolveMethod 沒(méi)有消息轉(zhuǎn)發(fā)呀!莫非我哪里漏掉了什么豺旬?再想想看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钠惩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子族阅,更是在濱河造成了極大的恐慌篓跛,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坦刀,死亡現(xiàn)場(chǎng)離奇詭異愧沟,居然都是意外死亡蔬咬,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)沐寺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)林艘,“玉大人,你說(shuō)我怎么就攤上這事混坞『” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵究孕,是天一觀的道長(zhǎng)啥酱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)厨诸,這世上最難降的妖魔是什么镶殷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮泳猬,結(jié)果婚禮上批钠,老公的妹妹穿的比我還像新娘。我一直安慰自己得封,他們只是感情好埋心,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著忙上,像睡著了一般拷呆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疫粥,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天茬斧,我揣著相機(jī)與錄音,去河邊找鬼梗逮。 笑死项秉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慷彤。 我是一名探鬼主播娄蔼,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼底哗!你這毒婦竟也來(lái)了岁诉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跋选,失蹤者是張志新(化名)和其女友劉穎涕癣,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體前标,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坠韩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年距潘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片同眯。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绽昼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出须蜗,到底是詐尸還是另有隱情,我是刑警寧澤目溉,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布明肮,位于F島的核電站,受9級(jí)特大地震影響缭付,放射性物質(zhì)發(fā)生泄漏柿估。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一陷猫、第九天 我趴在偏房一處隱蔽的房頂上張望秫舌。 院中可真熱鬧,春花似錦绣檬、人聲如沸足陨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)墨缘。三九已至,卻和暖如春零抬,著一層夾襖步出監(jiān)牢的瞬間镊讼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工平夜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝶棋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓忽妒,卻偏偏與公主長(zhǎng)得像玩裙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锰扶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 以下所有內(nèi)容屬筆者原創(chuàng), 如有雷同純屬巧合, 未經(jīng)允許不得轉(zhuǎn)載. OC中的方法調(diào)用實(shí)質(zhì)是發(fā)送消息(objc_ms...
    Homer1ynn閱讀 408評(píng)論 0 2
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過(guò) selector 快速查找 ...
    lylaut閱讀 1,824評(píng)論 2 3
  • 關(guān)于OC中的消息發(fā)送的實(shí)現(xiàn)献酗,在去年也看過(guò)一次,當(dāng)時(shí)有點(diǎn)不太理解坷牛,但是今年再看卻很容易理解罕偎。 我想這跟知識(shí)體系的構(gòu)建...
    咖啡綠茶1991閱讀 938評(píng)論 0 1
  • 今天在網(wǎng)絡(luò)上看資料時(shí)偶爾看到了objc_msgSend對(duì)selector的查找過(guò)程颜及。主要參數(shù)了下面的資料:http...
    飛到哪閱讀 493評(píng)論 0 1
  • 一切都天然存在甩苛,只需要換回來(lái)。你的選擇就是你的世界俏站,你的世界就是你的選擇 一讯蒲、137教育平臺(tái)近期開(kāi)課時(shí)間表: 05...
    靜心137閱讀 215評(píng)論 0 0