iOS Runtime 黑魔法方法交換(Method swizzling)

在實(shí)際開(kāi)發(fā)場(chǎng)景中,有時(shí)候我們需要在調(diào)用系統(tǒng)方法察净,或者某個(gè)類的方法的時(shí)候锈至,增加自己的一些邏輯操作,這時(shí)候可以采用 方法交換 的方式去實(shí)現(xiàn)這個(gè)需求们拙。這種方式也被稱為 黑魔法(Method swizzling)或者 hook突勇,網(wǎng)上也有很多這方面的文檔解釋埂奈,在這里主要是記錄一下,hook 的時(shí)候遇到的問(wèn)題。

場(chǎng)景一:對(duì)某個(gè)類自身的方法進(jìn)行 hook 操作

什么意思呢借宵?舉個(gè)例子,NSString 這個(gè)類欲间,有一個(gè) substringToIndex: 方法,這個(gè)方法是在 NSString+NSStringExtensionMethods 這樣的一個(gè)分類里面达址。

需求:在使用 substringToIndex: 方法的時(shí)候,希望能在里面增加一些邏輯判斷苛败,比如判斷當(dāng)前傳入的 index 是否在當(dāng)前字符串范圍之內(nèi)。

NSString *string = @"abcd";
NSLog(@"%@", [string substringToIndex:10]);

這里傳入的 10乳蛾,字符串沒(méi)有這么長(zhǎng)的長(zhǎng)度,如果直接使用系統(tǒng)的方法,程序運(yùn)行起來(lái)蹦魔,立馬發(fā)生閃退招盲。

substringToIndex超出范圍

下面,進(jìn)行 hook 操作

  • NSString 新建一個(gè)分類,并在 load 方法中進(jìn)行 hook 操作

    + (void)load {
        
        // 系統(tǒng)方法
        Method system_method = class_getInstanceMethod([self class], @selector(substringToIndex:));
        // 將要替換系統(tǒng)方法
        Method my_method = class_getInstanceMethod([self class], @selector(yxc_substringToIndex:));
        // 進(jìn)行交換
        method_exchangeImplementations(system_method, my_method);
    }
    
    - (NSString *)yxc_substringToIndex:(NSUInteger)to {
        
        // 判斷傳入的數(shù)值是否大于當(dāng)前字符串的范圍设拟,如果大于的話,取當(dāng)前字符串的最大長(zhǎng)度
        if (to > self.length) {
            to = self.length;
        }
        
        return [self yxc_substringToIndex:to];
    }
    

    這樣就 hook 完成了,查看結(jié)果:

    substringToIndex進(jìn)行 hook 之后結(jié)果

這樣看起來(lái)牢硅,hook 操作很簡(jiǎn)單,沒(méi)有什么問(wèn)題如筛,但是這只是一種情況擦剑。

場(chǎng)景二:對(duì)某個(gè)類的父類或者基類的方法進(jìn)行 hook 操作

下面,對(duì) init 這個(gè)方法進(jìn)行 hook 操作。

  • 因?yàn)?NSString 特殊性肉康,在這里不再用 NSString 進(jìn)行舉例了骑素,新建一個(gè) Person 類侠姑,繼承于 NSObject邦邦;再給 Person 類創(chuàng)建一個(gè)分類,然后按照上面的方式對(duì) Personinit 方法進(jìn)行 hook

    + (void)load {
        
        Class cls = [self class];
        
        Method system_method = class_getInstanceMethod(cls, @selector(init));
        Method my_method = class_getInstanceMethod(cls, @selector(yxc_init));
        
        method_exchangeImplementations(system_method, my_method);
    }
    
    - (instancetype)yxc_init {
        
        NSLog(@"%s", __func__);
        return [self yxc_init];
    }
    
  • 通過(guò) allocinit 創(chuàng)建一個(gè) Person 對(duì)象,并未出現(xiàn)異常。

  • 緊接著創(chuàng)建一個(gè) NSObject 對(duì)象捉偏,這時(shí)候問(wèn)題出現(xiàn)了讹躯,程序進(jìn)入死循環(huán),并且報(bào) yxc_init: 方法找不到。

    hook init方法報(bào)錯(cuò)

    分析:

    • init 方法并不是 Person 類本身的實(shí)例(對(duì)象)方法,而是父類 NSObject 的方法绕娘。由于 Person 本身沒(méi)有該方法升酣,所以 class_getInstanceMethod 獲取到的方法是通過(guò) Personsuperclass 指針從 NSObject 類中獲取到了 init 這個(gè)方法复颈。
    • method_exchangeImplementations 操作將 NSObjectinit 方法的實(shí)現(xiàn)與 Person 類的 yxc_init 方法的實(shí)現(xiàn)進(jìn)行互換了帜讲,這時(shí)候調(diào)用 init 方法實(shí)際上是調(diào)用了 yxc_init 方法。
    • 創(chuàng)建一個(gè) Person 對(duì)象時(shí)玷氏,調(diào)用 init 方法赞辩,運(yùn)行時(shí)會(huì)去查找 yxc_init 的實(shí)現(xiàn)想诅,因?yàn)?yxc_init 方法是 Person 自身的方法忘古,所以查找到了直接調(diào)用娘荡。(消息發(fā)送機(jī)制)
    • 而創(chuàng)建一個(gè) NSObject 對(duì)象時(shí),調(diào)用 init 方法,運(yùn)行時(shí)去查找 yxc_init 方法的時(shí)候,NSObject 是沒(méi)有這個(gè)方法,這個(gè)方法存在于 Person 類中柏蘑,所以查找完畢庞溜,還是找不到這個(gè)方法漫试,就拋異常了普泡。

正確的 hook 做法是砰嘁,先將 init 方法添加到 Person 類中口糕,如果這個(gè)類當(dāng)前有這個(gè)方法(而不是父類)孤里,則不添加,直接 exchange,否則添加了 init 方法,然后再將 yxc_init 方法的實(shí)現(xiàn)設(shè)置成 init 方法的實(shí)現(xiàn)。

+ (void)load {

    Class cls = [self class];

    // 1. 獲取到父類的 init 方法
    Method system_method = class_getInstanceMethod(cls, @selector(init));
    // 2. 獲取到當(dāng)前類的 yxc_init 方法
    Method my_method = class_getInstanceMethod(cls, @selector(yxc_init));
    // 3. 先將 init 方法添加到當(dāng)前類中,并且將 yxc_init 作為 init 方法的實(shí)現(xiàn)
    BOOL addSuccess = class_addMethod(cls,
                                      @selector(init),
                                      method_getImplementation(my_method),
                                      method_getTypeEncoding(my_method));
    // 4. 判斷 init 添加到當(dāng)前類中是否成功
    if (addSuccess) {
        // 4.1 方法添加成功,則意味著當(dāng)前類在添加之前并沒(méi)有 init 方法,添加成功后就進(jìn)行方法替換,將 init 方法的實(shí)現(xiàn)替換成 yxc_init 方法的實(shí)現(xiàn)
        class_replaceMethod(cls,
                            @selector(yxc_init),
                            method_getImplementation(system_method),
                            method_getTypeEncoding(system_method));
    } else {
        // 4.2 方法添加失敗,說(shuō)明當(dāng)前類已存在該方法,直接進(jìn)行方法交換
        method_exchangeImplementations(system_method, my_method);
    }
}

- (instancetype)yxc_init {
    
    NSLog(@"%s", __func__);
    return [self yxc_init];
}

運(yùn)行結(jié)果顯示:

正確 hook init方法結(jié)果

通過(guò)這樣的方式進(jìn)行對(duì) 父類或者基類 方法的 hook患整,最終沒(méi)有發(fā)現(xiàn)其他異常,以此記錄父阻。

最后封裝一下 hook 邏輯操作

/// hook 方法
/// @param cls 類
/// @param originSelector 將要 hook 掉的方法
/// @param swizzledSelector 新的方法
/// @param clsMethod 類方法
+ (void)hookMethod:(Class)cls originSelector:(SEL)originSelector swizzledSelector:(SEL)swizzledSelector classMethod:(BOOL)clsMethod {
    
    Method origin_method;
    Method swizzled_method;
    
    if (clsMethod) {
        // 類方法
        origin_method = class_getClassMethod(cls, originSelector);
        swizzled_method = class_getClassMethod(cls, swizzledSelector);
    } else {
        // 實(shí)例(對(duì)象)方法
        origin_method = class_getInstanceMethod(cls, originSelector);
        swizzled_method = class_getInstanceMethod(cls, swizzledSelector);
    }
    
    BOOL addSuccess = class_addMethod(cls,
                                      originSelector,
                                      method_getImplementation(swizzled_method),
                                      method_getTypeEncoding(swizzled_method)
                                      );
    if (addSuccess) {
        class_replaceMethod(cls,
                            swizzledSelector,
                            method_getImplementation(origin_method),
                            method_getTypeEncoding(origin_method)
                            );
    } else {
        method_exchangeImplementations(origin_method, swizzled_method);
    }
}

類簇(Class Clusters)

Class Clusters(類簇)是抽象工廠模式在iOS下的一種實(shí)現(xiàn)斟览,眾多常用類妓羊,如 NSStringNSArray硅则,NSDictionary穷吮,NSNumber都運(yùn)作在這一模式下,它是接口簡(jiǎn)單性和擴(kuò)展性的權(quán)衡體現(xiàn)缠诅,在我們完全不知情的情況下褥伴,偷偷隱藏了很多具體的實(shí)現(xiàn)類逊躁,只暴露出簡(jiǎn)單的接口。

官方文檔講解類簇

下面對(duì) NSArray 進(jìn)行類簇講解

系統(tǒng)會(huì)創(chuàng)建 __NSPlaceholderArray假勿、 __NSSingleObjectArrayI邦泄、 __NSArray0诚亚、 __NSArrayM 等一些類簇夷家,下面對(duì)這些類簇進(jìn)行 hook 操作

+ (void)load {
    
    [self hookOriginClass:NSClassFromString(@"__NSPlaceholderArray") currentClass:[NSArray class] originSelector:@selector(initWithObjects:count:) swizzledSelector:@selector(yxc_initWithObjects:count:) classMethod:NO];
    
    [self hookOriginClass:NSClassFromString(@"__NSSingleObjectArrayI") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex:) classMethod:NO];
    
    [self hookOriginClass:NSClassFromString(@"__NSArray0") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex1:) classMethod:NO];
    
    [self hookOriginClass:NSClassFromString(@"__NSArrayM") currentClass:[NSArray class] originSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(yxc_objectAtIndexedSubscript:) classMethod:NO];
}

這樣就對(duì)數(shù)組中的一些方法進(jìn)行 hook 完了,而且也并沒(méi)有什么問(wèn)題察迟。

到這里概荷,就有一個(gè)疑問(wèn):在這里替換同一個(gè) SELobjectAtIndex:慈鸠,而這個(gè)方法是屬于 NSArray 這個(gè)類缕题,為什么這里替換了兩次,彼此都沒(méi)有影響到,按理來(lái)說(shuō)根據(jù)同一個(gè) SEL 獲取到的 IMP 進(jìn)行 replace 或者 exchange姿搜,那么最后生效的應(yīng)該是最后一次進(jìn)行 hook 的方法實(shí)現(xiàn),但是經(jīng)過(guò)發(fā)現(xiàn)锰提,沒(méi)有受影響。

首先類簇是需要繼承于原來(lái)那個(gè)類,在原來(lái)那個(gè)類的基礎(chǔ)上衍生了許多類出來(lái),下面我們用代碼證明這一點(diǎn)呻右。

Class __NSArrayM = NSClassFromString(@"__NSArrayM");
Class __NSArray0 = NSClassFromString(@"__NSArray0");
Class __NSSingleObjectArrayI = NSClassFromString(@"__NSSingleObjectArrayI");
Class __NSPlaceholderArray = NSClassFromString(@"__NSPlaceholderArray");

NSLog(@"__NSArrayM -> superclass : %@", class_getSuperclass(__NSArrayM));
NSLog(@"__NSArray0 -> superclass : %@", class_getSuperclass(__NSArray0));
NSLog(@"__NSSingleObjectArrayI -> superclass : %@", class_getSuperclass(__NSSingleObjectArrayI));
NSLog(@"__NSPlaceholderArray -> superclass : %@", class_getSuperclass(__NSPlaceholderArray));

輸出結(jié)果:

NSArray 的類簇輸出父類

既然 SEL 是 NSArray 的方法罐韩,為什么在 hook 的時(shí)候,能 hook 到每個(gè)類簇對(duì)應(yīng)的想法?

猜想:是不是每個(gè)類簇州藕,都實(shí)現(xiàn)了 objectAtIndex: 這個(gè)方法待牵,導(dǎo)致根據(jù) SEL 獲取到方法實(shí)現(xiàn)是不相同的

下面進(jìn)行驗(yàn)證這個(gè)猜想

+ (void)load {
    
    [self hookOriginClass:NSClassFromString(@"__NSPlaceholderArray") currentClass:[NSArray class] originSelector:@selector(initWithObjects:count:) swizzledSelector:@selector(yxc_initWithObjects:count:) classMethod:NO];
    
    NSLog(@"交換前");
    [self logInfo];
    
    [self hookOriginClass:NSClassFromString(@"__NSSingleObjectArrayI") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex:) classMethod:NO];
    NSLog(@"__NSSingleObjectArrayI交換后");
    [self logInfo];
    
    [self hookOriginClass:NSClassFromString(@"__NSArray0") currentClass:[NSArray class] originSelector:@selector(objectAtIndex:) swizzledSelector:@selector(yxc_objectAtIndex1:) classMethod:NO];
    NSLog(@"__NSArray0交換后");
    [self logInfo];
    
    [self hookOriginClass:NSClassFromString(@"__NSArrayM") currentClass:[NSArray class] originSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(yxc_objectAtIndexedSubscript:) classMethod:NO];
    
    
}

+ (void)logInfo {
    
    Class singleObjectCls = NSClassFromString(@"__NSSingleObjectArrayI");
    Class __NSArray0Cls = NSClassFromString(@"__NSArray0");
    Class currentCls = [self class];
    
    SEL selector = @selector(objectAtIndex:);
    
    Method singleObjectClsMethod = class_getInstanceMethod(singleObjectCls, selector);
    Method __NSArray0ClsMethod = class_getInstanceMethod(__NSArray0Cls, selector);
    Method currentMethod = class_getInstanceMethod(currentCls, selector);
    
    
    IMP singleObjectClsMethodIMP = method_getImplementation(singleObjectClsMethod);
    IMP __NSArray0ClsMethodIMP = method_getImplementation(__NSArray0ClsMethod);
    IMP currentIMP = method_getImplementation(currentMethod);
    
    NSLog(@"selector : %p, singleObjectClsMethod : %p, __NSArray0ClsMethod : %p, currentMethod : %p, singleObjectClsMethodIMP : %p, __NSArray0ClsMethodIMP : %p, currentIMP : %p",
          selector, singleObjectClsMethod, __NSArray0ClsMethod, currentMethod, singleObjectClsMethodIMP, __NSArray0ClsMethodIMP, currentIMP);
}

以上代碼踢俄,在 hook objectAtIndex: 方法之前和 hook 完一個(gè)、兩個(gè)之后對(duì) SEL晴及、class都办、MethodIMP 信息輸出

2020-11-02 20:02:42.598040+0800 Block[32615:646190] 交換前==================
2020-11-02 20:02:42.598612+0800 Block[32615:646190] selector : 0x7fff7256d44e, singleObjectClsMethod : 0x7fff85fe75c0, __NSArray0ClsMethod : 0x7fff85fcd260, currentMethod : 0x7fff85fda938, singleObjectClsMethodIMP : 0x7fff2e31daf6, __NSArray0ClsMethodIMP : 0x7fff2e41e13b, currentIMP : 0x7fff2e4629fe
2020-11-02 20:02:42.598878+0800 Block[32615:646190] __NSSingleObjectArrayI交換后======================
2020-11-02 20:02:42.598970+0800 Block[32615:646190] selector : 0x7fff7256d44e, singleObjectClsMethod : 0x7fff85fe75c0, __NSArray0ClsMethod : 0x7fff85fcd260, currentMethod : 0x7fff85fda938, singleObjectClsMethodIMP : 0x100003750, __NSArray0ClsMethodIMP : 0x7fff2e41e13b, currentIMP : 0x7fff2e4629fe
2020-11-02 20:02:42.599166+0800 Block[32615:646190] __NSArray0交換后===================
2020-11-02 20:02:42.599275+0800 Block[32615:646190] selector : 0x7fff7256d44e, singleObjectClsMethod : 0x7fff85fe75c0, __NSArray0ClsMethod : 0x7fff85fcd260, currentMethod : 0x7fff85fda938, singleObjectClsMethodIMP : 0x100003750, __NSArray0ClsMethodIMP : 0x1000037d0, currentIMP : 0x7fff2e4629fe

根據(jù)輸出的地址,可以看出根據(jù)不同的類簇獲取到的 Method 的方法結(jié)構(gòu)體地址也是不同一個(gè)琳钉,還有方法實(shí)現(xiàn)的地址也是不同一塊存儲(chǔ)空間势木,那就證明了猜想,根據(jù) SEL 獲取到的 Method 和 IMP 不同一個(gè)歌懒,可能是在每個(gè)類簇內(nèi)部對(duì)父類NSArray 的 objectAtIndex: 重新實(shí)現(xiàn)了一下啦桌,導(dǎo)致獲取到的并不是同一個(gè)。

為了驗(yàn)證是否子類重寫了父類的方法獲取到的并不是同一個(gè)(原理來(lái)講是不同一個(gè)的及皂,下面用代碼來(lái)驗(yàn)證這個(gè)想法)

新建一個(gè) Person 類甫男,并且聲明一個(gè) test 對(duì)象方法并實(shí)現(xiàn),然后創(chuàng)建一個(gè) Student 類验烧,繼承于 Person 類板驳,先不重寫父類的 test 方法。

子類未重寫父類方法獲取 classMethod和 IMP 地址.png

Student 未重寫父類 Persontest 方法碍拆,通過(guò)各自獲取到的 MethodIMP 的地址都是同一個(gè)

下面 Student 進(jìn)行重寫 test 方法

子類重寫父類方法獲取 classMethod和 IMP 地址.png

這時(shí)候發(fā)現(xiàn)若治,通過(guò)各自獲取 MethodIMP 的地址已經(jīng)不一樣了

這就驗(yàn)證了以上的猜想,在類簇內(nèi)部中感混,會(huì)對(duì)父類的一些方法進(jìn)行重寫端幼。這就導(dǎo)致可能某一個(gè)方法,在一個(gè)類簇中已經(jīng)進(jìn)行了 hook弧满,但是可能還是會(huì)出現(xiàn)方法名相同婆跑,但是類名不一樣的方法報(bào)錯(cuò),就像上面的 objectAtIndex: 方法一樣谱秽,如果只是對(duì) __NSSingleObjectArrayI 進(jìn)行了替換或者交換方法操作洽蛀,但是并沒(méi)有對(duì) __NSArray0 進(jìn)行同樣的操作,那么還是會(huì)出現(xiàn)索引超出界面疟赊,沒(méi)有達(dá)到預(yù)防的效果郊供。

class_addMethod 函數(shù)官方文檔描述

/** 
 * Adds a new method to a class with a given name and implementation.
 * 
 * @param cls The class to which to add a method.
 * @param name A selector that specifies the name of the method being added.
 * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
 * @param types An array of characters that describe the types of the arguments to the method. 
 * 
 * @return YES if the method was added successfully, otherwise NO 
 *  (for example, the class already contains a method implementation with that name).
 *
 * @note class_addMethod will add an override of a superclass's implementation, 
 *  but will not replace an existing implementation in this class. 
 *  To change an existing implementation, use method_setImplementation.
 */
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

下面對(duì) class_addMethod 進(jìn)行源碼分析

/// cls 類名
/// name 方法名
/// imp 方法實(shí)現(xiàn) 
/// types 方法簽名
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    // 沒(méi)有傳入 類名 直接返回 NO
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    // 開(kāi)始添加方法,對(duì)返回的結(jié)果進(jìn)行取反近哟,這里返回的是一個(gè) IMP 類型的結(jié)果
    return ! addMethod(cls, name, imp, types ?: "", NO);
}
/// cls 類名
/// name 方法名
/// imp 方法實(shí)現(xiàn)
/// types 方法簽名
/// replace 是否直接替換驮审,這里傳入的是 NO
static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;
    
    runtimeLock.assertLocked();

    checkIsKnownClass(cls);
    
    ASSERT(types);
    ASSERT(cls->isRealized());

    method_t *m;
    // 查找該方法
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists 已經(jīng)存在該方法
        if (!replace) {
            // 當(dāng) replace 為 NO 時(shí),直接返回該方法的實(shí)現(xiàn)
            result = m->imp;
        } else {
            // 當(dāng) replace 為 YES 時(shí)吉执,通過(guò) _method_setImplementation疯淫,直接將方法進(jìn)行替換
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 該方法不存在,對(duì)傳入的類進(jìn)行動(dòng)態(tài)添加方法
        auto rwe = cls->data()->extAllocIfNeeded();

        // fixme optimize
        // 創(chuàng)建一個(gè)方法列表
        method_list_t *newlist;
        // 分配內(nèi)存戳玫,并設(shè)置好 method_list_t 的值
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        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;
        // 準(zhǔn)備方法合并到該類中
        prepareMethodLists(cls, &newlist, 1, NO, NO);
        // 開(kāi)始合并
        rwe->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}
/// cls 類名
/// sel 方法名
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?
    // for 循環(huán)遍歷熙掺,根據(jù)傳入的 sel 方法進(jìn)行查找當(dāng)前類是否有該方法
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        // 查找傳入的方法列表是否有 sel 方法
        method_t *m = search_method_list_inline(*mlists, sel);
        // 找到了返回
        if (m) return m;
    }

    return nil;
}
ALWAYS_INLINE static method_t *search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    // 根據(jù)不同方式進(jìn)行查找
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        // 有序查找
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        // 無(wú)序查找
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}
// 二分查找方法
ALWAYS_INLINE static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

在使用 class_addMethod 添加方法時(shí),只會(huì)在當(dāng)前的類進(jìn)行查找方法咕宿,并不會(huì)像 消息機(jī)制 那樣在當(dāng)前類找不到币绩,就去父類查找蜡秽。在當(dāng)前類查找不到,就在當(dāng)前類動(dòng)態(tài)添加方法并設(shè)置實(shí)現(xiàn)缆镣;如果查找到了就不做操作芽突,返回查找到的方法實(shí)現(xiàn),然后通過(guò)取反操作董瞻,返回添加結(jié)果寞蚌。

class_replaceMethod 函數(shù)官方文檔描述

/** 
 * Replaces the implementation of a method for a given class.
 * 
 * @param cls The class you want to modify.
 * @param name A selector that identifies the method whose implementation you want to replace.
 * @param imp The new implementation for the method identified by name for the class identified by cls.
 * @param types An array of characters that describe the types of the arguments to the method. 
 *  Since the function must take at least two arguments—self and _cmd, the second and third characters
 *  must be “@:” (the first character is the return type).
 * 
 * @return The previous implementation of the method identified by \e name for the class identified by \e cls.
 * 
 * @note This function behaves in two different ways:
 *  - If the method identified by \e name does not yet exist, it is added as if \c class_addMethod were called. 
 *    The type encoding specified by \e types is used as given.
 *  - If the method identified by \e name does exist, its \c IMP is replaced as if \c method_setImplementation were called.
 *    The type encoding specified by \e types is ignored.
 */
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

查看 `` 源碼

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

    mutex_locker_t lock(runtimeLock);
    // 調(diào)用 addMethod 方法,但是此時(shí) addMethod 方法中的 replace 參數(shù)傳入的是 YES
    return addMethod(cls, name, imp, types ?: "", YES);
}

通過(guò)上面的 addMethod 源碼分析

  • 當(dāng)查找到方法已存在钠糊,直接通過(guò) _method_setImplementation 方法將傳入的方法實(shí)現(xiàn)挟秤,設(shè)置為查找目標(biāo)方法的實(shí)現(xiàn)
  • 當(dāng)查找到方法不存在,動(dòng)態(tài)添加到當(dāng)前類中

下面查看一下 _method_setImplementation 方法的實(shí)現(xiàn)原理

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

    if (!m) return nil;
    if (!imp) return nil;
    // 將舊的實(shí)現(xiàn)取出
    IMP old = m->imp;
    // 直接將新的實(shí)現(xiàn)方法設(shè)置到 method_t 的imp
    m->imp = imp;

    // Cache updates are slow if cls is nil (i.e. unknown)
    // RR/AWZ updates are slow if cls is nil (i.e. unknown)
    // fixme build list of classes whose Methods are known externally?

    flushCaches(cls);

    adjustCustomFlagsForMethodChange(cls, m);

    // 返回舊的實(shí)現(xiàn)
    return old;
}

查看 method_exchangeImplementations 的方法實(shí)現(xiàn)原理

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

    mutex_locker_t lock(runtimeLock);
    // 直接將傳入的兩個(gè) Method 方法實(shí)現(xiàn)進(jìn)行互換
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil);

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

查看 class_getInstanceMethod 方法底層實(shí)現(xiàn)原理

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(runtimeLock);
    return getMethod_nolock(cls, sel);
}
static method_t *getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;

    runtimeLock.assertLocked();

    // fixme nil cls?
    // fixme nil sel?

    ASSERT(cls->isRealized());

    // 遍歷當(dāng)前類是否有該方法眠蚂,如果沒(méi)有就遍歷父類
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }

    return m;
}
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // 在這里傳入的對(duì)象是元類對(duì)象
    return class_getInstanceMethod(cls->getMeta(), sel);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末煞聪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逝慧,更是在濱河造成了極大的恐慌昔脯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛臣,死亡現(xiàn)場(chǎng)離奇詭異云稚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)沈堡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門静陈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人诞丽,你說(shuō)我怎么就攤上這事鲸拥。” “怎么了僧免?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵刑赶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我懂衩,道長(zhǎng)撞叨,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任浊洞,我火速辦了婚禮牵敷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘法希。我一直安慰自己枷餐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布苫亦。 她就那樣靜靜地躺著毛肋,像睡著了一般奕锌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上村生,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音饼丘,去河邊找鬼趁桃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肄鸽,可吹牛的內(nèi)容都是我干的卫病。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼典徘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蟀苛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起逮诲,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤帜平,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梅鹦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體裆甩,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年齐唆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嗤栓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡箍邮,死狀恐怖茉帅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锭弊,我是刑警寧澤堪澎,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站廷蓉,受9級(jí)特大地震影響全封,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桃犬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一刹悴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧攒暇,春花似錦土匀、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)证杭。三九已至,卻和暖如春妒御,著一層夾襖步出監(jiān)牢的瞬間解愤,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工乎莉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留送讲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓惋啃,卻偏偏與公主長(zhǎng)得像哼鬓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子边灭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345