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

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

場景一:對某個類自身的方法進行 hook 操作

什么意思呢?舉個例子,NSString 這個類,有一個 substringToIndex: 方法,這個方法是在 NSString+NSStringExtensionMethods 這樣的一個分類里面。

需求:在使用 substringToIndex: 方法的時候,希望能在里面增加一些邏輯判斷,比如判斷當前傳入的 index 是否在當前字符串范圍之內。

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

這里傳入的 10,字符串沒有這么長的長度,如果直接使用系統的方法,程序運行起來,立馬發生閃退。

substringToIndex超出范圍

下面,進行 hook 操作

  • NSString 新建一個分類,并在 load 方法中進行 hook 操作

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

    這樣就 hook 完成了,查看結果:

    substringToIndex進行 hook 之后結果

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

場景二:對某個類的父類或者基類的方法進行 hook 操作

下面,對 init 這個方法進行 hook 操作。

  • 因為 NSString 特殊性,在這里不再用 NSString 進行舉例了,新建一個 Person 類,繼承于 NSObject;再給 Person 類創建一個分類,然后按照上面的方式對 Personinit 方法進行 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 創建一個 Person 對象,并未出現異常。

  • 緊接著創建一個 NSObject 對象,這時候問題出現了,程序進入死循環,并且報 yxc_init: 方法找不到。

    hook init方法報錯

    分析:

    • init 方法并不是 Person 類本身的實例(對象)方法,而是父類 NSObject 的方法。由于 Person 本身沒有該方法,所以 class_getInstanceMethod 獲取到的方法是通過 Personsuperclass 指針從 NSObject 類中獲取到了 init 這個方法。
    • method_exchangeImplementations 操作將 NSObjectinit 方法的實現與 Person 類的 yxc_init 方法的實現進行互換了,這時候調用 init 方法實際上是調用了 yxc_init 方法。
    • 創建一個 Person 對象時,調用 init 方法,運行時會去查找 yxc_init 的實現,因為 yxc_init 方法是 Person 自身的方法,所以查找到了直接調用。(消息發送機制)
    • 而創建一個 NSObject 對象時,調用 init 方法,運行時去查找 yxc_init 方法的時候,NSObject 是沒有這個方法,這個方法存在于 Person 類中,所以查找完畢,還是找不到這個方法,就拋異常了。

正確的 hook 做法是,先將 init 方法添加到 Person 類中,如果這個類當前有這個方法(而不是父類),則不添加,直接 exchange,否則添加了 init 方法,然后再將 yxc_init 方法的實現設置成 init 方法的實現。

+ (void)load {

    Class cls = [self class];

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

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

運行結果顯示:

正確 hook init方法結果

通過這樣的方式進行對 父類或者基類 方法的 hook,最終沒有發現其他異常,以此記錄。

最后封裝一下 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 {
        // 實例(對象)方法
        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下的一種實現,眾多常用類,如 NSStringNSArrayNSDictionaryNSNumber都運作在這一模式下,它是接口簡單性和擴展性的權衡體現,在我們完全不知情的情況下,偷偷隱藏了很多具體的實現類,只暴露出簡單的接口。

官方文檔講解類簇

下面對 NSArray 進行類簇講解

系統會創建 __NSPlaceholderArray__NSSingleObjectArrayI__NSArray0__NSArrayM 等一些類簇,下面對這些類簇進行 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];
}

這樣就對數組中的一些方法進行 hook 完了,而且也并沒有什么問題。

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

首先類簇是需要繼承于原來那個類,在原來那個類的基礎上衍生了許多類出來,下面我們用代碼證明這一點。

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

輸出結果:

NSArray 的類簇輸出父類

既然 SEL 是 NSArray 的方法,為什么在 hook 的時候,能 hook 到每個類簇對應的想法?

猜想:是不是每個類簇,都實現了 objectAtIndex: 這個方法,導致根據 SEL 獲取到方法實現是不相同的

下面進行驗證這個猜想

+ (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 完一個、兩個之后對 SELclassMethodIMP 信息輸出

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

根據輸出的地址,可以看出根據不同的類簇獲取到的 Method 的方法結構體地址也是不同一個,還有方法實現的地址也是不同一塊存儲空間,那就證明了猜想,根據 SEL 獲取到的 Method 和 IMP 不同一個,可能是在每個類簇內部對父類NSArray 的 objectAtIndex: 重新實現了一下,導致獲取到的并不是同一個。

為了驗證是否子類重寫了父類的方法獲取到的并不是同一個(原理來講是不同一個的,下面用代碼來驗證這個想法)

新建一個 Person 類,并且聲明一個 test 對象方法并實現,然后創建一個 Student 類,繼承于 Person 類,先不重寫父類的 test 方法。

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

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

下面 Student 進行重寫 test 方法

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

這時候發現,通過各自獲取 MethodIMP 的地址已經不一樣了

這就驗證了以上的猜想,在類簇內部中,會對父類的一些方法進行重寫。這就導致可能某一個方法,在一個類簇中已經進行了 hook,但是可能還是會出現方法名相同,但是類名不一樣的方法報錯,就像上面的 objectAtIndex: 方法一樣,如果只是對 __NSSingleObjectArrayI 進行了替換或者交換方法操作,但是并沒有對 __NSArray0 進行同樣的操作,那么還是會出現索引超出界面,沒有達到預防的效果。

class_addMethod 函數官方文檔描述

/** 
 * 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);

下面對 class_addMethod 進行源碼分析

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

    mutex_locker_t lock(runtimeLock);
    // 開始添加方法,對返回的結果進行取反,這里返回的是一個 IMP 類型的結果
    return ! addMethod(cls, name, imp, types ?: "", NO);
}
/// cls 類名
/// name 方法名
/// imp 方法實現
/// 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 已經存在該方法
        if (!replace) {
            // 當 replace 為 NO 時,直接返回該方法的實現
            result = m->imp;
        } else {
            // 當 replace 為 YES 時,通過 _method_setImplementation,直接將方法進行替換
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 該方法不存在,對傳入的類進行動態添加方法
        auto rwe = cls->data()->extAllocIfNeeded();

        // fixme optimize
        // 創建一個方法列表
        method_list_t *newlist;
        // 分配內存,并設置好 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;
        // 準備方法合并到該類中
        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 循環遍歷,根據傳入的 sel 方法進行查找當前類是否有該方法
    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);
    
    // 根據不同方式進行查找
    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 添加方法時,只會在當前的類進行查找方法,并不會像 消息機制 那樣在當前類找不到,就去父類查找。在當前類查找不到,就在當前類動態添加方法并設置實現;如果查找到了就不做操作,返回查找到的方法實現,然后通過取反操作,返回添加結果。

class_replaceMethod 函數官方文檔描述

/** 
 * 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);
    // 調用 addMethod 方法,但是此時 addMethod 方法中的 replace 參數傳入的是 YES
    return addMethod(cls, name, imp, types ?: "", YES);
}

通過上面的 addMethod 源碼分析

  • 當查找到方法已存在,直接通過 _method_setImplementation 方法將傳入的方法實現,設置為查找目標方法的實現
  • 當查找到方法不存在,動態添加到當前類中

下面查看一下 _method_setImplementation 方法的實現原理

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

    if (!m) return nil;
    if (!imp) return nil;
    // 將舊的實現取出
    IMP old = m->imp;
    // 直接將新的實現方法設置到 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);

    // 返回舊的實現
    return old;
}

查看 method_exchangeImplementations 的方法實現原理

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

    mutex_locker_t lock(runtimeLock);
    // 直接將傳入的兩個 Method 方法實現進行互換
    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 方法底層實現原理

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());

    // 遍歷當前類是否有該方法,如果沒有就遍歷父類
    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;
    // 在這里傳入的對象是元類對象
    return class_getInstanceMethod(cls->getMeta(), sel);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374