iOS Method Swizzle 源码分析

作者: 学习路上一个远行者 | 来源:发表于2017-12-09 10:44 被阅读48次

iOS Method Swizzle 代码

+ (void)swizzleOriginalSEL:(SEL)originalSEL withSwizzlingSEL:(SEL)swizzlingSEL {
    
    1.Method originalMethod = class_getInstanceMethod(self, originalSEL);
    2.Method swizzlingMethod = class_getInstanceMethod(self, swizzlingSEL);
    
    3.Boolean isAddMethod = class_addMethod(self, originalSEL,
                                          method_getImplementation(swizzlingMethod),
                                          method_getTypeEncoding(swizzlingMethod));
    if (isAddMethod) {
        4.class_replaceMethod(self, swizzlingSEL,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
       5. method_exchangeImplementations(originalMethod, swizzlingMethod);
    }
}

平常我们用的方法都是method_exchangeImplementations,其实这种用法是错误的,我们正确的替换方法应该上面的代码。我直接说明上面的代码与直接调用method_exchangeImplementations的区别:为了防止我们所替换子类的实例方法是继承与父类,而且我们并没有在子类中重写该方法。如果直接调用method_exchangeImplementations方法那么我们会直接替换了父类中的方法实现,导致继承此父类的所有子类对应的改实例方法的实现被转换,如果大家已经明白了这个道理就可以绕行了(😆)。我也会介绍一下这几个方法的源码,以及一些runtime基本的知识。

class_replaceMethod 和 class_addMethod 源码分析

class_addMethod源码

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    // 如果cls为nil,直接返回NO,添加方法失败
    if (!cls) return NO;
    // 加锁,我们只需要明白这是加锁为了防止多线程访避免数据同步问题
    rwlock_writer_t lock(runtimeLock);
    // 实现函数,看最后一个函数我们传递的是NO,这个参数要注意。因为我们下面可以看到,我们分析class_replaceMehod源码中也会看到我们调用其实也是addMehtod方法不过最后一个参数是YES
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

class_replaceMethod源码

// 代码注释请参考class_addMethod
IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;

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

下面分析addMethod源码

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;
    // 加锁
    runtimeLock.assertWriting();
    // 一些断言函数,第一个是保证types必须有值,第二个保证cls是已经实现的状态
    assert(types);
    assert(cls->isRealized());

    method_t *m;
    // getMethodNoSuper_nolock 这个方法非常的关键,他的含义是只在本类中查找是否含有方法,而不再父类中查找。
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // 可以从本类中查找方法
        if (!replace) {
            // replace 我们在class_addMethod方法中传递的是NO,所以我们这个方法会走这一步,或者其他。如果我们查找的方法结构体中函数指针是有值,那么返回YES,但是class_addMethod会对result取反,所以我们知道如果我们将要添加的method,在cls中存在,并且已经有了函数指针那么我们添加方法失败。
            result = m->imp;
        } else {
            //  repalce 我们在class_replaceMethod中传递的是YES,class_replaceMethod可能会走这一步。
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // 没有在本类中查找到对应的方法,则我们在class中添加method,而method的结构体系:class->method_array_t(methods)->method_list_t->method
        // method 的体系会在后面讲解
        method_list_t *newlist;
        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);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);
        // 返回result是NO,那么class_addMethod取反,那么返回YES
        result = nil;
    }

    return result;
}

现在我们分析_getMethodNoSuper_nolock()方法,其实这个方法是我们在class中查找method的根本方法,我们在class_getInstanceMethod中也是调用了这方法。

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?
    // for循环cls(本类)属性mehtods,methods中包含每一个method_list_t,之后再method_list_t中根据sel来查找method
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

分析search_method_list方法

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        // 在已经进行过排序的method列表中查找
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // 这个是我们关注的。我们根据sel作为key,来查找method
        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;
}

根据上面的代码我们可以大致了解到,如果没有查找到对应的method,我们会给class中的methods属性中添加一个method_list_t,而method_list_t中储存着method。那么我们可以得知method的储存结构是数组中包含着数组,子数组中包含着Method,这样设计的目的是区分开Method来自哪里,是来自自己本身、父类、分类、扩展、runtime添加。当我们大致了解了method结构我们在来看看一些addMethod方法中else这一部分的代码

// 创建一个method_list_t *newlist结构体,并且分配内存,和循环标识
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
//对newlist进行复制操作,
newlist->count = 1;
//first,这个属性的类型是Method,所以这里相当于给Method赋值,name就是SEL
newlist->first.name = name;
// 方法type进行赋值
newlist->first.types = strdupIfMutable(types);
// 函数指针进行赋值
newlist->first.imp = imp;
// 对method_list_t进行准备操作
prepareMethodLists(cls, &newlist, 1, NO, NO);
// 将生成好的method_list_t放在methods(method_array_t)中,也就是重新生成一个method_list_t放在class中
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls);
// 返回result是NO,那么class_addMethod取反,那么返回YES
result = nil;

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.
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches
    //这一部分是我们关注的
    return _class_getMethod(cls, sel);
}

static Method _class_getMethod(Class cls, SEL sel)
{
    rwlock_reader_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());
    // 其实在查找method中,我们依然依靠了getMethodNoSuper_nolock方法,只不过是在一个我们会根据superclass来进行循环
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->superclass;
    }

    return m;
}

代码步骤分析

  • 1 & 2获取method结构体,class_getInstanceMethod获取的method可能是在本身的class中获取,或者是在superclass中获取
  • 3 首先查找original mehod是否可以在本类中查找到,如果查到不到,则在本类中添加一个method,而且method的实现方法是 swizzle method的实现方法
  • 4 如果添加方法成功,首先说明了本类中没有original method,我们在第一步中获取的original method 是在父类中的,所以我们自己在本类中创建了一个new original method, 现在new original method 与第一步获取的original method 没有任何的关系。那么现在 new original MethodIMPswizzle IMP,那么我现在调用class_replaceMethod可以把可能从父类中拿到的original method中的实现方法赋值给swizzle method,这样就完成了方法替换
  • 5 如果方法添加没有成功,说明本类中含有这个method,我们直接调用method_exchangeImplementations进行方法IMP替换

Load 方法中调用 method swizzle

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleOriginalSEL:@selector(sendEvent:) withSwizzlingSEL:@selector(swizzle_sendEvent:)];
    });
}

为什么要在load方法中增加dispathc_once,因为load方法中本来就会调用一次,我在一篇简书评论中看见一位人这样评论,防止人为调用load,即使这种场景非常少,我感觉这这个解答相当给力,作为程序猿我们应该有意识来方法来进行人为防范,不要想着load真的只会调用一次。如果对load和initialize 不是很了解,可以看看我的另外一篇文(就当增加一些人气了,不然的话太尴尬了。)

引用

objc4-723

相关文章

  • iOS Method Swizzle 源码分析

    iOS Method Swizzle 代码 平常我们用的方法都是method_exchangeImplementa...

  • iOS逆向之反HOOK的基本防护

    iOS逆向之Method Swizzle iOS逆向之fishHook原理探究 iOS逆向之fishHook怎么通...

  • iOS Swizzle Method

    1.实例方法交换 2.类方法交换 3.class一点总结 项目地址:https://github.com/hkkh...

  • iOS Swizzle method

    在理解这一套东西之前,我们先理清楚几个函数的意义 1.class_addMethod(aClass, origin...

  • Hook原理

    HOOK概述 HOOK示意图 iOS中HOOK技术的几种方式 Method Swizzle 2.fishhook ...

  • iOS swizzle Method小记

    Method类型是一个objc_method结构体指针,而结构体objc_method有三个成员,方法交换(Met...

  • 《iOS 逆向》010-Hook简单使用

    iOS中HOOK技术的几种方式 1、Method Swizzle 利用OC的Runtime特性,动态改变SEL(方...

  • 平安好房iOS开发团队技术周报(第五期)

    本期导读:本期周报主要包括Xcode扩展、Method Swizzle、Git分支实践等内容。 资讯 1) iOS...

  • HOOK技术

    iOS中HOOK技术的几种方式 Method Swizzle利用OC的Runtime特性,动态改变SEL(方法编号...

  • HOOK原理

    hook(钩子)处理特殊的消息机制 iOS中HOOK技术的几种方式 1、Method Swizzle利用OC的Ru...

网友评论

    本文标题:iOS Method Swizzle 源码分析

    本文链接:https://www.haomeiwen.com/subject/mahuixtx.html