Method Swizzling:方法混淆,顾名思义将两个方法交换,即由A-AIMP,B-BIMP变成A-BIMP,B-AIMP。
方法混淆原理:每个类都有一个方法(Method)列表,Method包含SEL和IMP的对应关系,方法交换就是将SEL和IMP的关系断开,并和新的IMP生成新的对应关系。
相关函数介绍
//获取通过SEL获取一个方法
class_getInstanceMethod
//获取一个方法的实现
method_getImplementation
//給方法添加实现
class_addMethod
//用一个方法的实现替换另一个方法的实现
class_replaceMethod
//交换两个方法的实现
method_exchangeImplementations
方法混淆应保证唯一性和原子性,由于ios内部实现不可见,因此方法混淆可能会导致代码结构改变,因此应该调用原始实现来保证操作的正常运行。
应用举例:
(1)hook:修改UIImage的imageNamed方法。
@implementation UIImage (Hook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = object_getClass([self class]);
SEL oriSEL = @selector(imageNamed:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
SEL cusSEL = @selector(myImageNamed:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
method_exchangeImplementations(oriMethod, cusMethod);
}
});
}
+ (UIImage *)myImageNamed:(NSString *)name {
return [self myImageNamed:newName];
}
@end
代码调用了[self myImageNamed:newName],看起来会陷入死循环,但其实myImageNamed的实现已经是imageNamed(IMP),因此该调用相当于调用系统原来的生成图片的实现。
(2)面向切面编程: 数据统计
@implementation UIButton (Hook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [self class];
SEL oriSEL = @selector(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
SEL cusSEL = @selector(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
method_exchangeImplementations(oriMethod, cusMethod);
}
});
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[CountTool addClickCount];
[self mySendAction:action to:target forEvent:event];
}
@end
Method Swizzling是OC动态性的最好诠释,深入地去学习并理解其特性,将有助于我们在业务量不断增大的同时还能保持代码的低耦合度,降低维护的工作量和难度。