iOS - Method swizzling源码分析

802 阅读3分钟

小谷底层探索合集

  • 诶呀,起因:这两天在项目中遇到了个bug,我的大leader把代码改了。。我又不敢说啥。。传说中的黑魔法(iOS - Method swizzling)他不会用,准备稍微写写嘲讽他😆。

  • 网上也有好多相关的文章,但他不查,这事闹的~~ (谁让人家是领导呢。。)

1. Method swizzling 的简单使用

    1. 常见的需求:在进来页面后添加打点统计~

这个时候,当然也可以手动添加。不过肯定会添加到你呻吟。

    1. 嘿嘿,适当的时机引入了 Method swizzling,可谓舒服的要死
    1. 简单写下使用代码

兄弟们,我就把注释直接写在代码中了。(今天主要是想跟兄弟们分析下源码~)


//我们先创建一个分类
#import "UIViewController+XG.h”
//引入运行时
#import <objc/runtime.h>

@implementation UIViewController (XG)

// 只有在load里面 swizzling才是最安全的~
+ (void) load {
    
    //swizzling 改变了全局状态,我们要保证他执行一次,而且在每个线程运行时都是可用的,GCD单例就比较完美了
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //实例方法,我们用这个
        Class class = [self class];
        
        // @selector
        SEL originalSeletor = @selector(viewDidAppear:);
        SEL swizzlingSelector = @selector(xg_textViewDidAppear:);

        //method
        Method originalMethod = class_getInstanceMethod(class, originalSeletor);
        Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSelector);
        //类方法
//        Class class2 = object_getClass((id)self);
//        Method originalMethod2 = class_getClassMethod(class2, <#SEL  _Nonnull name#>)
//        Method swizzlingMethod2 = class_getClassMethod(class2, <#SEL  _Nonnull name#>)
        
        // sel (绑定method) ->  imp
        // 加入方法是否成功
        BOOL didAddMethod = class_addMethod(class, originalSeletor, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
        
        if (didAddMethod) {
            //成功的话。替换操作
            class_replaceMethod(class, swizzlingSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else{
            //没有成功的话直接交换
            method_exchangeImplementations(originalMethod, swizzlingMethod);
        }
    });
}

- (void) xg_textViewDidAppear:(BOOL)animated{
    [self xg_textViewDidAppear:animated];
    NSLog(@"xg_textViewDidAppear : %@",self);
}

@end

这个时候进入界面的时候就会打印了~

    1. 直接看输出吧。

Method swizzling使用起来还是挺舒服的~~

2. Method swizzling源码

本来我打算看汇编调用的,不过看见新出来了 objc4-818.2源码 ,心血来潮弄了个工程调试了~

2.1. 锁定源码

    1. 大多数兄弟们分析问题都会看源码。这个做法是非常的👍。(我是😆,经常打着看源码的幌子划水)

 

    1. 我们来分析源码了~
    • 2.1 主要方法有三个 class_addMethodclass_replaceMethodmethod_exchangeImplementations

 

    1. 我们先分析 class_addMethodclass_replaceMethod

源码实现~

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}


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

    mutex_locker_t lock(runtimeLock);
    return addMethod(cls, name, imp, types ?: "", YES);
}
    1. 区别分析~
    1. 断点调试~(我新建一个XGPerson,然后调用方法)

他们最终都会走到 addMethod 方法

2.2. Method swizzling源码分析

    1. addMethod 方法
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) {
            result = m->imp(false);
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
        newlist->count = 1;
        auto &first = newlist->begin()->big();
        first.name = name;
        first.types = strdupIfMutable(types);
        first.imp = imp;

        addMethods_finish(cls, newlist);
        result = nil;
    }

    return result;
}
    1. 兄弟们,我画了思维导图,虽然画的比较low,哈哈
    1. method_exchangeImplementations源码分析
void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    mutex_locker_t lock(runtimeLock);

    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();

    m1->setImp(imp2);
    m2->setImp(imp1);


    // 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, __func__, [sel1, sel2, imp1, imp2](Class c){
        return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
    });

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

这个代码写的就明明白白了~

而且这个也非常生动形象的说明了 sel 和 imp 的关系。

3. 结语

  • 希望这篇文章对大家有帮助吧(我个菜鸡可能没有资格说~),重要的是,希望这篇文章被leader看见~😆