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

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

場(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,字符串沒有這么長的長度,如果直接使用系統(tǒng)的方法,程序運(yùn)行起來,立馬發(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)前字符串的最大長度
        if (to > self.length) {
            to = self.length;
        }
        
        return [self yxc_substringToIndex:to];
    }
    

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

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

這樣看起來,hook 操作很簡單,沒有什么問題,但是這只是一種情況。

場(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];
    }
    
  • 通過 allocinit 創(chuàng)建一個(gè) Person 對(duì)象,并未出現(xiàn)異常。

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

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

    分析:

    • init 方法并不是 Person 類本身的實(shí)例(對(duì)象)方法,而是父類 NSObject 的方法。由于 Person 本身沒有該方法,所以 class_getInstanceMethod 獲取到的方法是通過 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 是沒有這個(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)前類在添加之前并沒有 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 方法添加失敗,說明當(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é)果

通過這樣的方式進(jìn)行對(duì) 父類或者基類 方法的 hook,最終沒有發(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),眾多常用類,如 NSString,NSArrayNSDictionary,NSNumber都運(yùn)作在這一模式下,它是接口簡單性和擴(kuò)展性的權(quán)衡體現(xiàn),在我們完全不知情的情況下,偷偷隱藏了很多具體的實(shí)現(xià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 完了,而且也并沒有什么問題。

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

首先類簇是需要繼承于原來那個(gè)類,在原來那個(gè)類的基礎(chǔ)上衍生了許多類出來,下面我們用代碼證明這一點(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、classMethod、IMP 信息輸出

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è)(原理來講是不同一個(gè)的,下面用代碼來驗(yàn)證這個(gè)想法)

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

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

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

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

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

這時(shí)候發(fā)現(xiàn),通過各自獲取 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)行了替換或者交換方法操作,但是并沒有對(duì) __NSArray0 進(jìn)行同樣的操作,那么還是會(huì)出現(xiàn)索引超出界面,沒有達(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)
{
    // 沒有傳入 類名 直接返回 NO
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    // 開始添加方法,對(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í),通過 _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);
        // 開始合并
        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
        // 無序查找
        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),然后通過取反操作,返回添加結(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);
}

通過上面的 addMethod 源碼分析

  • 當(dāng)查找到方法已存在,直接通過 _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)前類是否有該方法,如果沒有就遍歷父類
    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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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