Method swizzling为甚么要用class_addMethod?

OC中做方法交换时,没有直接使用method_exchangeImplementations,而是与class_addMethod一起使用。

    if(class_addMethod([self class], test11, method_getImplementation(method1), "v@:"))
    {
        class_replaceMethod([self class], test1, method_getImplementation(method11), "v@:");
    }else{
        method_exchangeImplementations(method1, method3);
    }

那为什么这么用,会有什么好处。先做个对比测试,看下面的代码。

//
//  MethodBase.h
//  BlockTest
//
//  Created by net263 on 2022/1/12.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MethodBase : NSObject
-(void)test3;
-(void)exchangeBase;
@end

NS_ASSUME_NONNULL_END

//
//  MethodBase.m
//  BlockTest
//
//  Created by net263 on 2022/1/12.
//

#import "MethodBase.h"
#import <objc/runtime.h>
@implementation MethodBase
- (void)test3{
    [self test11];
    NSLog(@"test3");
}

- (void)exchangeBase{
    SEL test1 = @selector(test1);
    SEL test11 = @selector(test11);
    SEL test3 = @selector(test3);

    Method method1 = class_getInstanceMethod([self class], test1);
    Method method11 = class_getInstanceMethod([self class], test11);
    Method method3 = class_getInstanceMethod([self class], test3);
    
    
    method_exchangeImplementations(method11, method1);

//    if(class_addMethod([self class], test11, method_getImplementation(method1), "v@:"))
//    {
//        class_replaceMethod([self class], test1, method_getImplementation(method11), "v@:");
//    }else{
//        method_exchangeImplementations(method11, method1);
//    }
    
}

-(void)test11{
    NSLog(@"test11");
}
@end
//
//  MethodEx.h
//  BlockTest
//
//  Created by net263 on 2022/1/12.
//

#import <Foundation/Foundation.h>
#import "MethodBase.h"
NS_ASSUME_NONNULL_BEGIN

@interface MethodEx : MethodBase
@end

NS_ASSUME_NONNULL_END

//
//  MethodEx.m
//  BlockTest
//
//  Created by net263 on 2022/1/12.
//

#import "MethodEx.h"
#import <objc/objc.h>
#import <objc/runtime.h>
@implementation MethodEx
- (void)test1{
    NSLog(@"test1");
}
@end

MethodEx继承MethodBase,MethodBase中有test11及test3方法,还有执行交换的方法exchangeBase。
子类MethodBase有test1方法。
先简单了解下exchangeBase交换方法的内容:

- (void)exchangeBase{
    SEL test1 = @selector(test1);
    SEL test11 = @selector(test11);
    SEL test3 = @selector(test3);

    Method method1 = class_getInstanceMethod([self class], test1);
    Method method11 = class_getInstanceMethod([self class], test11);
    Method method3 = class_getInstanceMethod([self class], test3);
    
    
    method_exchangeImplementations(method11, method1);

//    if(class_addMethod([self class], test11, method_getImplementation(method1), "v@:"))
//    {
//        class_replaceMethod([self class], test1, method_getImplementation(method11), "v@:");
//    }else{
//        method_exchangeImplementations(method11, method1);
//    }
    
}

这段代码是将test1与test11进行交换。
外部调用的代码如下:

    MethodEx *methodEx = [[MethodEx alloc] init];
    NSLog(@"交换前");
    [methodEx test3];
    NSLog(@"--------------");
    NSLog(@"交换后");
    [methodEx exchangeBase];
    [methodEx test3];
    NSLog(@"--------------");
    NSLog(@"新的子类实例");
    MethodEx *methodEx1 = [[MethodEx alloc] init];
    [methodEx1 test3];
    
    NSLog(@"--------------");
    NSLog(@"父类实例");
    MethodBase *base = [[MethodBase alloc] init];
    [base test3];
  • 先看直接调用method_exchangeImplementations的情况
2-01-12 16:31:19.906047+0800 BlockTest[1112:462703] 交换前
2022-01-12 16:31:19.906088+0800 BlockTest[1112:462703] test11
2022-01-12 16:31:19.906133+0800 BlockTest[1112:462703] test3
2022-01-12 16:31:19.906166+0800 BlockTest[1112:462703] --------------
2022-01-12 16:31:19.906204+0800 BlockTest[1112:462703] 交换后
2022-01-12 16:31:19.906499+0800 BlockTest[1112:462703] test1
2022-01-12 16:31:19.906741+0800 BlockTest[1112:462703] test3
2022-01-12 16:31:19.906788+0800 BlockTest[1112:462703] --------------
2022-01-12 16:31:19.906834+0800 BlockTest[1112:462703] 新的子类实例
2022-01-12 16:31:19.906871+0800 BlockTest[1112:462703] test1
2022-01-12 16:31:19.906950+0800 BlockTest[1112:462703] test3
2022-01-12 16:31:19.907036+0800 BlockTest[1112:462703] --------------
2022-01-12 16:31:19.907401+0800 BlockTest[1112:462703] 父类实例
2022-01-12 16:31:19.907440+0800 BlockTest[1112:462703] test1
2022-01-12 16:31:19.907472+0800 BlockTest[1112:462703] test3

从打印结果看出,交换后,新的子类与父类实例都是执行交换后的操作,父类的操作被替换了。

  • 使用class_addMethod的情况
2022-01-12 16:33:59.968165+0800 BlockTest[1118:463568] 交换前
2022-01-12 16:33:59.968207+0800 BlockTest[1118:463568] test11
2022-01-12 16:33:59.968240+0800 BlockTest[1118:463568] test3
2022-01-12 16:33:59.968273+0800 BlockTest[1118:463568] --------------
2022-01-12 16:33:59.968309+0800 BlockTest[1118:463568] 交换后
2022-01-12 16:33:59.968441+0800 BlockTest[1118:463568] test1
2022-01-12 16:33:59.968546+0800 BlockTest[1118:463568] test3
2022-01-12 16:33:59.968711+0800 BlockTest[1118:463568] --------------
2022-01-12 16:33:59.968923+0800 BlockTest[1118:463568] 新的子类实例
2022-01-12 16:33:59.969158+0800 BlockTest[1118:463568] test1
2022-01-12 16:33:59.969194+0800 BlockTest[1118:463568] test3
2022-01-12 16:33:59.969226+0800 BlockTest[1118:463568] --------------
2022-01-12 16:33:59.969266+0800 BlockTest[1118:463568] 父类实例
2022-01-12 16:33:59.969343+0800 BlockTest[1118:463568] test11
2022-01-12 16:33:59.969403+0800 BlockTest[1118:463568] test3

从打印结果看出,交换后,新的子类执行的交换后的操作,父类的操作还是交换前的操作。class_addMethod后,将test11的实现添加到了子类中,不影响父类的操作。
总结:
要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现原始方法"test11" ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 test11 ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容