什么是Swizzling Method
method Swizzling是OC runtime机制提供的一种可以动态替换方法的实现的技术,我们可以利用它替换系统或者自定义类的方法实现,来满足我们特别的需求
建立在Runtime之上的Swizzling Method
Demo在此
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 _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
复制代码
作用:获取一个类的实例方法
cls : 方法所在的类
name: 选择子的名称(选择子就是方法名称)
class_getClassMethod
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
复制代码
作用:获取一个类的类方法
cls : 方法所在的类
name: 选择子名称
method_getImplementation
OBJC_EXPORT IMP _Nonnull
method_getImplementation(Method _Nonnull m);
复制代码
作用: 根据方法获取方法的指针
m : 方法
method_getTypeEncoding OBJC_EXPORT const char * _Nullable method_getTypeEncoding(Method _Nonnull m); 复制代码 作用: 获取方法的参数和返回值类型描述 m : 方法
class_addMethod
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
复制代码
作用: 给类添加一个新方法和该方法的实现
返回值: yes,表示添加成功; No,表示添加失败
cls: 将要添加方法的类
name: 将要添加的方法名
imp: 实现这个方法的指针
types: 要添加的方法的返回值和参数
method_exchangeImplementations
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
复制代码
作用:交换两个方法 class_replaceMethod
OBJC_EXPORT IMP _Nullable
class_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 = dogFood
Test[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 kilo
Test[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<NSCopying>)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, 也可以在
Demo中找到JRSwizzle源码
- 改变某类视图的大小,例如盖面app中所有UIButton的大小
我们就可以这样实现:\
#import "UIButton+Size.h"
#import <objc/runtime.h>
@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
复制代码
项目Demo在此
Swizzling Method的使用注意事项
后续补充...如在文中发现任何错误,请及时告知,第一时间更正;