Runtime 對IMP進行操作的api源碼解析

class_addMethod 給一個類添加方法

  1. 正常添加一個繼承鏈中不存在的方法, 結(jié)果能夠正常使用

  2. 如果這個類本身就有該方法的申明和實現(xiàn)則添加失敗

  3. 如果這個類本身僅僅有該方法的申明那么是可以添加成功的

  4. 如果這個類本身僅僅有該方法的實現(xiàn)則會添加失敗(預(yù)料之中,因為所謂申明僅僅是編譯器的事,編譯完也就沒有.h的事了)

  5. 如果這個類沒有該方法,但是父類有呢?結(jié)果是父類不會影響到子類添加方法的結(jié)果, 依然可以添加成功

源碼分析

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}


static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    // 加鎖
    runtimeLock.assertLocked();
    
    // 檢測該cls是否存在
    checkIsKnownClass(cls);
    
    // 方法簽名不可為空
    assert(types);
    
    // 類必須是初始化后的
    assert(cls->isRealized());

    // 根據(jù) 方法名去cls這個類(不沿著繼承鏈向上查詢)查詢有沒有對應(yīng)的方法實現(xiàn)
    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // 如果有的話, 因為replace傳的是NO, 也不會替換原來實現(xiàn)
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 沒有則生成一個新的方法并放在cls的方法列表最后
        method_list_t *newlist;
        // 1.開辟空間
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        // 2.初始化
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;
        
        // 3.準備添加
        prepareMethodLists(cls, &newlist, 1, NO, NO);
        // 4.添加
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

method_setImplementation 修改一個方法的實現(xiàn)部分

IMP method_setImplementation(Method m, IMP imp)
{
    // 可以看出來修改一個方法的實現(xiàn), 是針對Method這個結(jié)構(gòu)體本身的行為
    mutex_locker_t lock(runtimeLock);
    return _method_setImplementation(Nil, m, imp);
}


static IMP _method_setImplementation(Class cls, method_t *m, IMP imp)
{
    // 加鎖
    runtimeLock.assertLocked();

    // 判空
    if (!m) return nil;
    if (!imp) return nil;

    // 直接method_t這個結(jié)構(gòu)體的imp成員指向了新的imp
    IMP old = m->imp;
    m->imp = imp;

    flushCaches(cls);

    updateCustomRR_AWZ(cls, m);

    return old;
}

method_exchangeImplementations 交換兩個方法的實現(xiàn)

void method_exchangeImplementations(Method m1, Method m2)
{
    // 判空
    if (!m1  ||  !m2) return;

    // 加鎖
    mutex_locker_t lock(runtimeLock);

    // 直接Method這個結(jié)構(gòu)體的imp成員指向進行了交換就完事了
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;
    
    flushCaches(nil);

    updateCustomRR_AWZ(nil, m1);
    updateCustomRR_AWZ(nil, m2);
}

method_getImplementation 直接返回了Method這個結(jié)構(gòu)體的imp

IMP method_getImplementation(Method m)
{
    return m ? m->imp : nil;
}

class_getMethodImplementation 獲取一個類的對應(yīng)方法名的IMP

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}


// 執(zhí)行查找imp和轉(zhuǎn)發(fā)的代碼
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 如果cache是YES阶女,則從緩存中查找IMP闲坎。如果是從cache3函數(shù)進來,則不會執(zhí)行cache_getImp()函數(shù)
    if (cache) {
        // 通過cache_getImp函數(shù)查找IMP,查找到則返回IMP并結(jié)束調(diào)用
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.read();

    // 判斷類是否已經(jīng)被創(chuàng)建,如果沒有被創(chuàng)建埋泵,則將類實例化
    if (!cls->isRealized()) {
        // 對類進行實例化操作
        realizeClass(cls);
    }

    // 第一次調(diào)用當前類的話抑党,執(zhí)行initialize的代碼
    if (initialize  &&  !cls->isInitialized()) {
        // 對類進行初始化,并開辟內(nèi)存空間
        _class_initialize (_class_getNonMetaClass(cls, inst));
    }
    
 retry:    
    runtimeLock.assertReading();

    // 嘗試獲取這個類的緩存
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
    
    {
        // 如果沒有從cache中查找到拧咳,則從方法列表中獲取Method
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 如果獲取到對應(yīng)的Method,則加入緩存并從Method獲取IMP
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    {
        unsigned attempts = unreasonableClassCount();
        // 循環(huán)獲取這個類的緩存IMP 或 方法列表的IMP
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // 獲取父類緩存的IMP
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 如果發(fā)現(xiàn)父類的方法囚灼,并且不再緩存中骆膝,在下面的函數(shù)中緩存方法
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            
            // 在父類的方法列表中,獲取method_t對象灶体。如果找到則緩存查找到的IMP
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // 如果沒有找到阅签,則嘗試動態(tài)方法解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        triedResolver = YES;
        goto retry;
    }

    // 如果沒有IMP被發(fā)現(xiàn),并且動態(tài)方法解析也沒有處理蝎抽,則進入消息轉(zhuǎn)發(fā)階段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

class_replaceMethod 將一個類的原有方法用新方法進行替換,如果原有方法為空,則直接調(diào)用class_addMethod將新方法添加上

IMP _Nullable class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

    mutex_locker_t lock(runtimeLock);
    return addMethod(cls, name, imp, types ?: "", YES);
}


static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    // 加鎖
    runtimeLock.assertLocked();
    
    // 檢測該cls是否存在
    checkIsKnownClass(cls);
    
    // 方法簽名不可為空
    assert(types);
    
    // 類必須是初始化后的
    assert(cls->isRealized());

    // 根據(jù) 方法名去cls這個類(不沿著繼承鏈向上查詢)查詢有沒有對應(yīng)的方法實現(xiàn)
    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // 如果有的話, 因為replace傳的是YES, 會替換原來實現(xiàn)
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 沒有則生成一個新的方法并放在cls的方法列表最后
        method_list_t *newlist;
        // 1.開辟空間
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        // 2.初始化
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;
        
        // 3.準備添加
        prepareMethodLists(cls, &newlist, 1, NO, NO);
        // 4.添加
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末政钟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子樟结,更是在濱河造成了極大的恐慌养交,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狭吼,死亡現(xiàn)場離奇詭異层坠,居然都是意外死亡,警方通過查閱死者的電腦和手機刁笙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門破花,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疲吸,你說我怎么就攤上這事座每。” “怎么了摘悴?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵峭梳,是天一觀的道長。 經(jīng)常有香客問我蹂喻,道長葱椭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任口四,我火速辦了婚禮孵运,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蔓彩。我一直安慰自己治笨,他們只是感情好驳概,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旷赖,像睡著了一般顺又。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上等孵,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天稚照,我揣著相機與錄音,去河邊找鬼俯萌。 笑死锐锣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绳瘟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼姿骏,長吁一口氣:“原來是場噩夢啊……” “哼糖声!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起分瘦,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蘸泻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嘲玫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悦施,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年去团,在試婚紗的時候發(fā)現(xiàn)自己被綠了抡诞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡土陪,死狀恐怖昼汗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鬼雀,我是刑警寧澤顷窒,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站源哩,受9級特大地震影響鞋吉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜励烦,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一谓着、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧崩侠,春花似錦漆魔、人聲如沸坷檩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矢炼。三九已至,卻和暖如春阿纤,著一層夾襖步出監(jiān)牢的瞬間句灌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工欠拾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胰锌,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓藐窄,卻偏偏與公主長得像资昧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荆忍,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344