美文网首页ios底层原理
Method Swizzling需要class_addMetho

Method Swizzling需要class_addMetho

作者: Cwwng | 来源:发表于2020-10-28 18:30 被阅读0次

    理论:Method Swizzling本质上就是对IMP和SEL的交换。
    在OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,SEL对应一个IMP(一个IMP可以对应多个SEL),通过IMP调用方法。
    每个类中都有一个Dispatch Table,作用就是将类中的SEL和IMP对应。而我们Method Swizzling就是对这个Dispatch Table进行操作。

    1、Method Swizzling源码分析,核心代码就是交换两个Method的IMP函数指针。

    void method_exchangeImplementations(Method m1, Method m2)
    {
        if (!m1  ||  !m2) return;
    
        //加锁
        mutex_locker_t lock(runtimeLock);
    
        //指针指向交换
        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);
    
        //看方法名是根据方法变更 适配class 中 custom flag状态
        // 当方法更改其IMP时,更新自定义RR和AWZ
        adjustCustomFlagsForMethodChange(nil, m1);
        adjustCustomFlagsForMethodChange(nil, m2);
    }
    

    2、不加class_addMethod的奔溃

    #import "Person.h"
    
    @implementation Person
    
    - (void)eatAction {
        
        NSLog(@"Person eat");
    }
    
    @end
    

    Son是Person的子类

    #import "Son.h"
    #import <objc/runtime.h>
    
    @implementation Son
    
    +(void)load {
        
        Method method1 = class_getInstanceMethod(self, @selector(eatAction));
        Method method2 = class_getInstanceMethod(self, @selector(sonEatAction));
        
        method_exchangeImplementations(method1, method2);
    }
    
    - (void)sonEatAction {
        
        NSLog(@"sonEatAction");
        [self sonEatAction];
    }
    
    @end
    

    调用

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
        Person *person = [[Person alloc] init];
        [person eatAction];
    }
    

    奔溃日志

    2020-10-28 17:52:27.077942+0800 Test[91632:3868212] sonEatAction
    2020-10-28 17:52:27.078362+0800 Test[91632:3868212] -[Person sonEatAction]: unrecognized selector sent to instance 0x600002fcc030
    2020-10-28 17:52:27.098770+0800 Test[91632:3868212] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person sonEatAction]: unrecognized selector sent to instance 0x600002fcc030'
    

    分析:
    eatAction在子类Son中没有实现,在父类Person中实现。
    class_getInstanceMethod是按照继承链查找方法。
    eatAction为父类方法,而本类没有。直接交换后,Person类的IMP为sonEatAction。调用奔溃。

    3、正确写法 - 需要class_addMethod

    #import "Son.h"
    #import <objc/runtime.h>
    
    @implementation Son
    
    +(void)load {
        
        Method method1 = class_getInstanceMethod(self, @selector(eatAction));
        Method method2 = class_getInstanceMethod(self, @selector(sonEatAction));
        
        BOOL result = class_addMethod(self, @selector(eatAction), method_getImplementation(method2), method_getTypeEncoding(method2));
        if (result) {
            class_replaceMethod(self, @selector(sonEatAction), method_getImplementation(method1), method_getTypeEncoding(method1));
        }else {
            method_exchangeImplementations(method1, method2);
        }
    }
    
    - (void)sonEatAction {
        
        NSLog(@"sonEatAction");
        [self sonEatAction];
    }
    
    @end
    

    分析:
    1、若eatAction为父类方法,先调用class_addMethod将其添加到本类中。
    2、若添加成功,则eatAction本来不存在于本class。调用class_replaceMethod替换。
    3、若添加不成功,则eatAction存在于本class。直接交换。
    源码分析:class_addMethod和class_replaceMethod都是调用addMethod方法。区别:replace=YES。

    源码分析

    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);
    }
    
    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;
        //判断本类是否有此方法。NoSuper说明不会沿着继承链查找,只在本类查找
        if ((m = getMethodNoSuper_nolock(cls, name))) {
            // already exists
            if (!replace) {
                // replace=NO 直接获取结果
                result = m->imp;
            } else {
                // replace=YES 将实现覆盖
                result = _method_setImplementation(cls, m, imp);
            }
        } else {
            auto rwe = cls->data()->extAllocIfNeeded();
    
            // fixme optimize
            // 本类中不存在此方法
            // 创建一个新的方法列表,赋值属性
            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);
            
            //插入现有的方法列表
            rwe->methods.attachLists(&newlist, 1);
            
            //刷新缓存
            flushCaches(cls);
    
            result = nil;
        }
    
        return result;
    }
    

    4、总结

    1、Method Swizzling时需要class_addMethod。
    作用:防止父类和子类方法实现的交换。遵循设计模式:里氏替换原则。
    2、class_addMethod和class_replaceMethod都是调用addMethod方法。
    区别:
    addMethod -> replace=NO -> 方法直接返回。
    replaceMethod -> replace=YES -> 方法覆盖。

    相关文章

      网友评论

        本文标题:Method Swizzling需要class_addMetho

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