objc源碼之Method消息發(fā)送

Objc源碼之對象創(chuàng)建alloc和init
Objc源碼之initialize實(shí)現(xiàn)
Objc源碼之Load方法實(shí)現(xiàn)
Objc源碼之NSObject和isa
Objc源碼之引用計(jì)數(shù)實(shí)現(xiàn)
objc源碼之Method消息發(fā)送

前言

?? 在我們進(jìn)行方法調(diào)用的時(shí)候,我們的對象是如何找到我們的方法呢?這個(gè)問題大家基本都知道是通過isa找到的,實(shí)例對象通過isa找到類對象,在類對象中查找方法,類對象通過isa指針找到元類,在元類對象中查找,那么在這個(gè)過程中究竟查找過程是怎么實(shí)現(xiàn)的,除了查找方法,還會(huì)進(jìn)行哪些操作呢?這篇文章我們通過objc的源碼來看下具體的查找過程。

一、方法調(diào)用過程

TestObject *obj = [TestObject new];
[obj test];

我們以實(shí)例對象的方法調(diào)用為例,來說明一下方法的調(diào)用過程:
1.首先[obj test]會(huì)轉(zhuǎn)換成objc_msgSend(self,@ selector(test))函數(shù)調(diào)用。

2.obj通過isa指針找到類對象,實(shí)例對象的方法列表存在于類對象中。

3.類對象是一個(gè)objc_class結(jié)構(gòu)體,objc_class結(jié)構(gòu)中存在一個(gè)cache_t類型的cache,從cache里面的bucket_t中通過@ selector(test)為key來查找方法實(shí)現(xiàn)IMP。

4.如果objc_class的cache中沒有查找到,就通過class_data_bits_t來獲取class_rw_t來獲取中的methods方法列表來查找test方法。

5.如果類對象中沒有查找到對應(yīng)的方法,就通過objc_class結(jié)構(gòu)體中的superclass來找到對象的父類對象,然后重復(fù)3、4、5這個(gè)過程,如果還沒有查找到,就會(huì)到到根類NSObject,NSObject的父對象是nil的(參考下面經(jīng)典的類關(guān)系圖),這個(gè)時(shí)候如果還沒有查找到,就開始進(jìn)入消息轉(zhuǎn)發(fā)了。

類關(guān)系圖.png

6.進(jìn)入消息轉(zhuǎn)發(fā)階段以后。

  • 首先是調(diào)用resolveInstanceMethod:或者resolveClassMethod:,這一步可以給當(dāng)前類添加方法,來響應(yīng)這個(gè)過程。
  • 調(diào)用forwardingTargetForSelector:,這一步是尋找一個(gè)備援接受者來響應(yīng)這個(gè)而方法。
  • 調(diào)用methodSignatureForSelector和forwardInvocation,完整的消息轉(zhuǎn)發(fā),通過NSInvocation來響應(yīng)這個(gè)方法。


    消息轉(zhuǎn)發(fā)過程.png

7.如果上述過程都沒有響應(yīng),那么則會(huì)crash,報(bào)unrecognized selector sent to instance的錯(cuò)誤。

二、objc_msgSend

?? 當(dāng)編譯器遇到一個(gè)方法調(diào)用時(shí),它會(huì)將方法的調(diào)用翻譯成以下函數(shù)中的一個(gè) objc_msgSend、objc_msgSend_stret、objc_msgSendSuper 和 objc_msgSendSuper_stret。發(fā)送給對象的父類的消息會(huì)使用 objc_msgSendSuper 有數(shù)據(jù)結(jié)構(gòu)作為返回值的方法會(huì)使用 objc_msgSendSuper_stret 或 objc_msgSend_stret 其它的消息都是使用 objc_msgSend 發(fā)送的。

?? 在objc_msgSend是OC實(shí)例對象和類對象發(fā)送消息的核心引擎,用來查找方法實(shí)現(xiàn),對性能要求較高,因此這一部分是通過匯編代碼來編寫的。下面是歐陽大哥通過匯編代碼,翻譯的c代碼深入解構(gòu)objc_msgSend函數(shù)的實(shí)現(xiàn)

//下面的結(jié)構(gòu)體中只列出objc_msgSend函數(shù)內(nèi)部訪問用到的那些數(shù)據(jù)結(jié)構(gòu)和成員。

/*
其實(shí)SEL類型就是一個(gè)字符串指針類型,所描述的就是方法字符串指針
*/
typedef char * SEL;

/*
IMP類型就是所有OC方法的函數(shù)原型類型。
*/
typedef id (*IMP)(id self, SEL _cmd, ...); 


/*
  方法名和方法實(shí)現(xiàn)桶結(jié)構(gòu)體
*/
struct bucket_t  {
    SEL  key;       //方法名稱
    IMP imp;       //方法的實(shí)現(xiàn),imp是一個(gè)函數(shù)指針類型
};

/*
   用于加快方法執(zhí)行的緩存結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體其實(shí)就是一個(gè)基于開地址沖突解決法的哈希桶。
*/
struct cache_t {
    struct bucket_t *buckets;    //緩存方法的哈希桶數(shù)組指針,桶的數(shù)量 = mask + 1
    int  mask;        //桶的數(shù)量 - 1
    int  occupied;   //桶中已經(jīng)緩存的方法數(shù)量。
};

/*
    OC對象的類結(jié)構(gòu)體描述表示,所有OC對象的第一個(gè)參數(shù)保存是的一個(gè)isa指針。
*/
struct objc_object {
  void *isa;
};

/*
   OC類信息結(jié)構(gòu)體,這里只展示出了必要的數(shù)據(jù)成員。
*/
struct objc_class : objc_object {
    struct objc_class * superclass;   //基類信息結(jié)構(gòu)體。
    cache_t cache;    //方法緩存哈希表
    //... 其他數(shù)據(jù)成員忽略。
};



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

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

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

上面的代碼,總結(jié)一下:
1.對象空值判斷,這個(gè)就是在OC中為什么給空對象發(fā)送消息,不crash的原因。
2. 獲取或者構(gòu)造對象的isa數(shù)據(jù),通過isa查找類或者元類
3. 遍歷緩存哈希桶并查找緩存中的方法實(shí)現(xiàn),通過cache查找是否命中緩存
4. 執(zhí)行方法實(shí)現(xiàn)或方法未命中緩存處理函數(shù)objc_msgSend_uncached

未命中緩存

三、lookUpImpOrForward

lookUpImpOrForward是方法調(diào)用過程的核心類,方法的查找、類的初始化、initialize都可能在這里面調(diào)用。

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    //1. 緩存查找
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.lock();
    checkIsKnownClass(cls);
     //2. 類是否實(shí)現(xiàn)
    if (!cls->isRealized()) {
        realizeClass(cls);
    }
     //3. 類是否初始化
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
    }

    
 retry:    
    runtimeLock.assertLocked();

   
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // 4.方法列表查找,查找到以后,進(jìn)行緩存。
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 5.父類方法列表查找,查找到進(jìn)行緩存。
    {
        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 {
                    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;
            }
        }
    }

    // 6.如果還沒有查找到。進(jìn)入消息轉(zhuǎn)發(fā)resolveMethod方法

    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;
    }

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

 done:
    runtimeLock.unlock();

    return imp;
}

lookUpImpOrForward方法有如下過程:
1. 緩存中查找方法
2. 類是否實(shí)現(xiàn)
3.類是否初始化
4.方法列表查找,查找到以后,進(jìn)行緩存。
5.父類方法列表查找,查找到進(jìn)行緩存。
6.如果還沒有查找到。進(jìn)入消息轉(zhuǎn)發(fā)resolveMethod方法
這里的方法查找過程,我在第一部分的方法調(diào)用過程中都有描述過,我重點(diǎn)說一下2和3,這兩部分是做什么。

  • 類是否實(shí)現(xiàn),這一部分主要是判斷類是否是第一次調(diào)用,第一次調(diào)用的時(shí)候,class_rw_t可能還沒有創(chuàng)建好,因?yàn)榉椒ㄊ谴嬖谶@里面的,所以要保證類已經(jīng)實(shí)現(xiàn)。
  • 類是否初始化,這一部分主要是初始化類的一些參數(shù),包括isa指針,同時(shí)我們熟悉的Initialize方法也是在這里調(diào)用的。

四、消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)是在運(yùn)行時(shí)進(jìn)行的,大致分為三個(gè)階段:
第一階段是先檢查接收者,看是否能通過runtime動(dòng)態(tài)添加一個(gè)方法,來處理這個(gè)方法;
第二階段就是備援接收者,看看有沒有對象可以響應(yīng)這個(gè)方法。
第二階段就是把該消息的全部信息封裝到NSInvocation對象中,看哪個(gè)對象能否處理,如果還無法處理,則報(bào)錯(cuò)unrecognized selector sent to instance。

1.動(dòng)態(tài)方法解析

// 類方法專用
+ (BOOL)resolveClassMethod:(SEL)sel
// 對象方法專用
+ (BOOL)resolveInstanceMethod:(SEL)sel

2.備援接收者

- (id)forwardingTargetForSelector:(SEL)aSelector

3.完整消息轉(zhuǎn)發(fā)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

在方法簽名的過程中,注意簽名符號(hào):

*          代表  char * 
char BOOL  代表  c
:          代表  SEL 
^type      代表  type *
@          代表  NSObject * 或 id
^@         代表  NSError ** 
#          代表  NSObject 
v          代表  void
消息轉(zhuǎn)發(fā)過程.png

五、總結(jié)

方法的調(diào)用過程:
1.緩存查找
2.查找當(dāng)前類的緩存及方法。
3.查找父類的緩存及方法
4.消息轉(zhuǎn)發(fā)

參考:
objc4-750源碼
從源代碼看 ObjC 中消息的發(fā)送.md
深入解構(gòu)objc_msgSend函數(shù)的實(shí)現(xiàn)
iOS消息轉(zhuǎn)發(fā)機(jī)制實(shí)例
iOS的消息轉(zhuǎn)發(fā)機(jī)制詳解

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

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

  • 關(guān)于OC中的消息發(fā)送的實(shí)現(xiàn),在去年也看過一次,當(dāng)時(shí)有點(diǎn)不太理解,但是今年再看卻很容易理解。 我想這跟知識(shí)體系的構(gòu)建...
    咖啡綠茶1991閱讀 1,064評論 0 1
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,983評論 2 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,621評論 1 32
  • 2019年1月3日,星期四,陰。 昨天忙活一天,晚上喝多了點(diǎn)。 雇主是上次帝都那小伙。事不繁重,安裝兩...
    距離負(fù)人閱讀 220評論 0 5
  • 那天我做了一個(gè)夢。 那個(gè)小小的家里,爺爺還坐在那張他專屬的板凳上,奶奶像平時(shí)一樣笨拙的忙進(jìn)忙出。 我似乎是一個(gè)局外...
    相佯閱讀 203評論 0 0

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