ios底层原理

Method Swizzling需要class_addMetho

2020-10-28  本文已影响0人  Cwwng

理论: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 -> 方法覆盖。

上一篇下一篇

猜你喜欢

热点阅读