博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Swizzling Method
阅读量:6863 次
发布时间:2019-06-26

本文共 13758 字,大约阅读时间需要 45 分钟。

什么是Swizzling Method


method Swizzling是OC runtime机制提供的一种可以动态替换方法的实现的技术,我们可以利用它替换系统或者自定义类的方法实现,来满足我们特别的需求

建立在Runtime之上的Swizzling Method

在此

Swizzling Method的实现原理

OC中的方法在runtime.h中的定义如下:

struct objc_method{
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE; }
method_name: 方法名
method_types: 方法类型,主要存储着方法的参数类型和返回值类型
method_imp: 方法的实现,函数指针
由此,我们也可以发现OC中的方法名是不包括参数类型的,也就是在runtime中方法名相同参数不同的方法会被视为同一个方法
原则上,方法的名称method_name和方法的实现method_imp是一一对应的,而Swizzling Method的原理就是动态的改变他们的对应关系,以达到替换方法的目的。

Runtime中和方法替换相关的函数

class_getInstanceMethod

OBJC_EXPORT Method _Nullableclass_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);复制代码

作用:获取一个类的实例方法

cls : 方法所在的类
name: 选择子的名称(选择子就是方法名称)

class_getClassMethod

OBJC_EXPORT Method _Nullableclass_getClassMethod(Class _Nullable cls, SEL _Nonnull name);复制代码

作用:获取一个类的类方法

cls : 方法所在的类
name: 选择子名称

method_getImplementation

OBJC_EXPORT IMP _Nonnullmethod_getImplementation(Method _Nonnull m);复制代码

作用: 根据方法获取方法的指针

m : 方法

method_getTypeEncoding OBJC_EXPORT const char * _Nullable method_getTypeEncoding(Method _Nonnull m); 复制代码 作用: 获取方法的参数和返回值类型描述 m : 方法

class_addMethod

OBJC_EXPORT BOOLclass_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,  const char * _Nullable types);复制代码

作用: 给类添加一个新方法和该方法的实现

返回值: yes,表示添加成功; No,表示添加失败
cls: 将要添加方法的类
name: 将要添加的方法名
imp: 实现这个方法的指针
types: 要添加的方法的返回值和参数

method_exchangeImplementations

OBJC_EXPORT voidmethod_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);复制代码

作用:交换两个方法 class_replaceMethod

OBJC_EXPORT IMP _Nullableclass_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,  const char * _Nullable types) ;复制代码

作用: 指定替换方法的实现 cls : 将要替换方法的类 name: 将要替换的方法名 imp: 新方法的指针 types: 新方法的返回值和参数描述

Swizzling Method的常见应用场景

  • 替换一个类的实例方法
    eg:替换UIViewController中的viewDidLoad方法
//将方法替换包装成函数待调用void MethodSwizzle(Class c, SEL oriSEL, SEL overrideSEL){    Method originMethod = class_getInstanceMethod(c, oriSEL);    Method swizzleMethod = class_getInstanceMethod(c, overrideSEL);        BOOL did_add_method = class_addMethod(c, oriSEL, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));//添加Method,其键值为originSelector,值为swizzleMethod的实现        if (did_add_method) {        NSLog(@"debugMsg: ViewController类中没有viewDidLoad方法(可能在其父类h中),所以先添加后替换");        class_replaceMethod(c, overrideSEL, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));    } else {        NSLog(@"debugMsg: 直接交换方法");        method_exchangeImplementations(originMethod, swizzleMethod);    }}//调用load方法+ (void)load{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        MethodSwizzle([self class], @selector(viewDidLoad), @selector(wn_viewDidLoad));    });}//替换方法的实现- (void)wn_viewDidLoad{    NSLog(@"调用了wn_viewDidLoad");    [self wn_viewDidLoad];}复制代码
  • 替换一个类的实例方法到另一个类中去实现
    这种情况一般用在当我们不清楚私有库的具体实现,只知道该类名称和该类的一个方法,此时我们需要hook这个类的方法到另一个新类中。
    eg: 我们hook Animal类中的有一个eat:方法\
//Animal.h@interface Animals : NSObject- (void)eat:(NSString *)food;@end//Animal.m@implementation Animals- (void)eat:(NSString *)food{    NSLog(@"food Name = %@",food);}复制代码

然后新建一个类Dog, hook eat:方法到Dog中。如下:

//Dog.m//方法替换的函数实现void SwizzleMethod(Class oriClass, Class overClass, SEL oriSEL, SEL overSEL){    Method origin_method = class_getInstanceMethod(oriClass, oriSEL);    Method swizzle_method = class_getInstanceMethod(overClass, overSEL);        //判断oriClass是否已经存在overSEL方法,若已存在,则return    BOOL exit_overSel = class_addMethod(oriClass, overSEL, method_getImplementation(swizzle_method), method_getTypeEncoding(swizzle_method));    if (!exit_overSel) return;        //获取oriClass的overSEL的Method实例    swizzle_method = class_getInstanceMethod(oriClass, overSEL);    if (!swizzle_method) return;        BOOL exit_origin = class_addMethod(oriClass, oriSEL, method_getImplementation(swizzle_method), method_getTypeEncoding(swizzle_method));    if (exit_origin) {        class_replaceMethod(oriClass, overSEL, method_getImplementation(origin_method), method_getTypeEncoding(origin_method));    } else {        method_exchangeImplementations(origin_method, swizzle_method);    }}//调用替换方法+ (void)load{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        SwizzleMethod(NSClassFromString(@"Animals"), [self class], NSSelectorFromString(@"eat:"), NSSelectorFromString(@"dog_eat:"));    });}//替换方法的实现- (void)dog_eat:(NSString *)food{    if ([food isEqualToString:@"dogFood"]) {        [self dog_eat:food];    } else {        NSLog(@"不是狗粮");    }}复制代码

我们来测试一下,在viewController中输出

- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.        Animals *animal = [[Animals alloc] init];    [animal eat:@"dogFood"];    [animal eat:@"pigFood"];}复制代码

打印结果为:

Test[9442:2104359] food Name = dogFoodTest[9442:2104359] 不是狗粮复制代码
  • 替换类方法 尝试替换Animals的类方法:run:
//Animals.h+ (void)run:(NSInteger)kilo;//Animals.m+ (void)run:(NSInteger)kilo{    NSLog(@"胜利了!!跑了 %ld kilo",(long)kilo);}//Animals (Run)void ExchangeMethod(Class class, SEL oriSEL, SEL exchangeSEL){    Method origin_method = class_getClassMethod(class, oriSEL);    Method exchange_method = class_getClassMethod(class, exchangeSEL);        if (!origin_method || !exchange_method) return;    IMP origin_imp = method_getImplementation(origin_method);    IMP swizzle_imp = method_getImplementation(exchange_method);        const char *origin_type= method_getTypeEncoding(origin_method);    const char *swizzle_type = method_getTypeEncoding(exchange_method);        Class metaClass = objc_getMetaClass(class_getName(class));    class_replaceMethod(metaClass, oriSEL, swizzle_imp, swizzle_type);    class_replaceMethod(metaClass, exchangeSEL, origin_imp, origin_type);}+ (void)load{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        ExchangeMethod([self class], @selector(run:), @selector(ex_run:));    });}+ (void)ex_run:(NSInteger)kilo{    if (kilo >= 10) {        [self ex_run:kilo];    } else {        NSLog(@"失败了!!=_= ,只跑了%ld kilo",kilo);    }}//ViewConroller.m- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.    [Animals run:11];    [Animals run:7];}复制代码

输出结果为:

Test[9494:2117807] 胜利了!!跑了 11 kiloTest[9494:2117807] 失败了!!=_= ,只跑了7 kilo复制代码

注意:

类方法的替换有个需要特别注意的地方:若把以下部分代码:

IMP origin_imp = method_getImplementation(origin_method);IMP swizzle_imp = method_getImplementation(exchange_method);const char *origin_type= method_getTypeEncoding(origin_method);const char *swizzle_type = method_getTypeEncoding(exchange_method);    Class metaClass = objc_getMetaClass(class_getName(class));class_replaceMethod(metaClass, oriSEL, swizzle_imp, swizzle_type);class_replaceMethod(metaClass, exchangeSEL, origin_imp, origin_type);复制代码

换成:

Class meta_class = objc_getMetaClass(class_getName(class));class_replaceMethod(meta_class, origin_selector, method_getImplementation(swizzle_method), swizzle_type = method_getTypeEncoding(swizzle_method));class_replaceMethod(meta_class, swizzle_selector, method_getImplementation(origin_method), method_getTypeEncoding(origin_method));复制代码

运行会直接crash,因为方法替换未成功,调用方法是导致了死循环。具体原因未知,但猜测肯定和MetaClass有关


  • 替换类簇中的方法
//MutableDictionary.m//方法实现void ExchangeDicMethod(Class oriClass, Class curClass, SEL oriSEL, SEL curSEL){    Method origin_method = class_getInstanceMethod(oriClass, oriSEL);    Method current_method = class_getInstanceMethod(curClass, curSEL);        if (!origin_method || !current_method) return;        class_replaceMethod(oriClass, curSEL, method_getImplementation(origin_method), method_getTypeEncoding(origin_method));    class_replaceMethod(oriClass, oriSEL, method_getImplementation(current_method), method_getTypeEncoding(current_method));}@implementation MutableDictionary//方法调用+ (void)load{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        ExchangeDicMethod(NSClassFromString(@"__NSDictionaryM"), [self class], @selector(setObject:forKey:), @selector(ex_setObject:forKey:));    });}//替换方法实现- (void)ex_setObject:(id)obj forKey:(id
)key{ if (obj && key) { NSLog(@"方法安全执行"); [self ex_setObject:obj forKey:key]; } else { NSLog(@"setObject:forKey:方法未执行,obj或者key未空"); }}@end复制代码

常见的Method Swizzling应用事例

  • 防止数组取值时越界crash
+ (void)load{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        [NSClassFromString(@"__NSArrayI") jr_swizzleMethod:@selector(objectAtIndex:)                                                withMethod:@selector(SF_ObjectAtIndex_NSArrayI:)                                                     error:nil];        [NSClassFromString(@"__NSArrayI") jr_swizzleMethod:@selector(objectAtIndexedSubscript:)                                                withMethod:@selector(SF_ObjectAtIndexedSubscript_NSArrayI:)                                                     error:nil];        [NSClassFromString(@"__NSArrayM") jr_swizzleMethod:@selector(objectAtIndex:)                                                withMethod:@selector(SF_ObjectAtIndex_NSArrayM:)                                                     error:nil];        [NSClassFromString(@"__NSArrayM") jr_swizzleMethod:@selector(objectAtIndexedSubscript:)                                                withMethod:@selector(SF_ObjectAtIndexedSubscript_NSArrayM:)                                                     error:nil];}- (id)SF_ObjectAtIndex_NSArrayI:(NSUInteger)index{    @autoreleasepool {        if (index >= self.count            || index < 0            || !self.count) {            @try {                return [self SF_ObjectAtIndex_NSArrayI:index];            } @catch (NSException *exception) {                NSLog(@"%@", [NSThread callStackSymbols]);                return nil;            } @finally {            }        } else {            return [self SF_ObjectAtIndex_NSArrayI:index];        }    }}- (id)SF_ObjectAtIndexedSubscript_NSArrayI:(NSUInteger)index{    @autoreleasepool {        if (index >= self.count            || index < 0            || !self.count) {            @try {                return [self SF_ObjectAtIndexedSubscript_NSArrayI:index];            } @catch (NSException *exception) {                NSLog(@"%@", [NSThread callStackSymbols]);                return nil;            } @finally {            }        } else {            return [self SF_ObjectAtIndexedSubscript_NSArrayI:index];        }    }}- (id)SF_ObjectAtIndex_NSArrayM:(NSUInteger)index{    @autoreleasepool {        if (index >= self.count            || index < 0            || !self.count) {            @try {                return [self SF_ObjectAtIndex_NSArrayM:index];            } @catch (NSException *exception) {                NSLog(@"%@", [NSThread callStackSymbols]);                return nil;            } @finally {            }        } else {            return [self SF_ObjectAtIndex_NSArrayM:index];        }    }}- (id)SF_ObjectAtIndexedSubscript_NSArrayM:(NSUInteger)index{    @autoreleasepool {        if (index >= self.count            || index < 0            || !self.count) {            @try {                return [self SF_ObjectAtIndexedSubscript_NSArrayM:index];            } @catch (NSException *exception) {                NSLog(@"%@", [NSThread callStackSymbols]);                return nil;            } @finally {            }        } else {            return [self SF_ObjectAtIndexedSubscript_NSArrayM:index];        }    }}复制代码

此处使用到了流行的包装库:, 也可以在中找到JRSwizzle源码

  • 改变某类视图的大小,例如盖面app中所有UIButton的大小
    我们就可以这样实现:\
#import "UIButton+Size.h"#import 
@implementation UIButton (Size)+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 增大所有按钮大小 Method origin_method = class_getInstanceMethod([self class], @selector(setFrame:)); Method replaced_method = class_getInstanceMethod([self class], @selector(miSetFrame:)); method_exchangeImplementations(origin_method, replaced_method); });}- (void)miSetFrame:(CGRect)frame{ frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width+20, frame.size.height+20); NSLog(@"设置按钮大小生效"); [self miSetFrame:frame];}@end复制代码
  • 处理按钮重复点击\

如果重复过快的点击同一个按钮,就会多次触发点击事件。我们可以这么解决:

//UIButton+QuickClick.h@interface UIButton (QuickClick)@property (nonatomic,assign) NSTimeInterval delayTime;@end//UIButton+QuickClick.m@implementation UIButton (QuickClick)static const char* delayTime_str = "delayTime_str";+ (void)load{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        Method originMethod =   class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));        Method replacedMethod = class_getInstanceMethod(self, @selector(miSendAction:to:forEvent:));        method_exchangeImplementations(originMethod, replacedMethod);    });}- (void)miSendAction:(nonnull SEL)action to:(id)target forEvent:(UIEvent *)event{    if (self.delayTime > 0) {        if (self.userInteractionEnabled) {            [self miSendAction:action to:target forEvent:event];        }        self.userInteractionEnabled = NO;        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,                                     (int64_t)(self.delayTime * NSEC_PER_SEC)),                       dispatch_get_main_queue(), ^{                           self.userInteractionEnabled = YES;                       });    }else{        [self miSendAction:action to:target forEvent:event];    }}- (NSTimeInterval)delayTime{    NSTimeInterval interval = [objc_getAssociatedObject(self, delayTime_str) doubleValue];    return interval;}- (void)setDelayTime:(NSTimeInterval)delayTime{    objc_setAssociatedObject(self, delayTime_str, @(delayTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end复制代码

项目在此

Swizzling Method的使用注意事项

后续补充...

如在文中发现任何错误,请及时告知,第一时间更正;

参考


转载地址:http://hbeyl.baihongyu.com/

你可能感兴趣的文章
课后练习----实现窗口的切换
查看>>
this 作用域
查看>>
Python3基础03_数据类型
查看>>
JS控制文本框输入的内容
查看>>
Tomcat7后台通过get接收数据处理乱码
查看>>
CI路径中如何去掉index.php
查看>>
精简ICO图标可减小EXE程序文件大小
查看>>
lstm caffe几个变量的含义
查看>>
博客园是不是应该取消反对按钮或者改进反对按钮
查看>>
重写equals()方法时,需要同时重写hashCode()方法
查看>>
Excel打印质量引起的异常及解决方案
查看>>
2.GET与POST的区别
查看>>
tyvj1172自然数拆分
查看>>
Makefile <网络转载>
查看>>
IO流的应用————小型资源管理器
查看>>
C++输入输出流格式控制(转)
查看>>
【C++】C++中的string类的用法总结
查看>>
new pc
查看>>
zabbix之 zabbix server 跟 agent 更换ip地址
查看>>
WebAPI 实现前后端分离的示例
查看>>