Method Swizzling需要class_addMethod

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355