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阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342