我们可以用 Method Swizzling 来交换两个方法的实现,以便达到 Hook 的效果;
例如:交换 ViewController 生命周期方法来实现页面埋点,或者在不影响原有的功能增加一些特殊的功能。
例如:替换系统一些方法,比如数组 add 方法,在add 方法内添加判断,防止添加nil 或者越界等crash
交换方法主要是利用到 Runtime 中的 class_addMethod、class_replaceMethod 和 method_exchangeImplementations 方法来实现的。
以下是 Method Swizzling 代码示例:
#pragma mark - 交换方法
+ (void)pxy_swizzleMethodWithOriginalSelector:(SEL)originalSeletor swizzledSelector:(SEL)swizzledSeletor {
Class class = [self class];
// 原有方法
Method originMethod = class_getInstanceMethod(class, originalSeletor);
// 替换原有方法的新方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSeletor);
//先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
BOOL didAddMethod = class_addMethod(class, originalSeletor,method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
class_replaceMethod(class, swizzledSeletor,
method_getImplementation(originMethod),
method_getTypeEncoding(originMethod));
} else { //添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
method_exchangeImplementations(originMethod, swizzledMethod);
}
}实例:用runtime处理数组越界
我们为NSObject写一个分类(SwizzleMethod),方便我们调用交换系统和自定义的方法:
@interface NSObject (SwizzleMethod)
/**
* 对系统方法进行替换(交换实例方法)
*
* @param systemSelector 被替换的方法
* @param swizzledSelector 实际使用的方法
* @param error 替换过程中出现的错误消息
*
* @return 是否替换成功
*/
+ (BOOL)SystemSelector:(SEL)systemSelector swizzledSelector:(SEL)swizzledSelector error:(NSError *)error;
@end
#import "NSObject+SwizzleMethod.h"
#import <objc/runtime.h>
@implementation NSObject (SwizzleMethod)
/**
* 对系统方法进行替换
*
* @param systemSelector 被替换的方法
* @param swizzledSelector 实际使用的方法
* @param error 替换过程中出现的错误消息
*
* @return 是否替换成功
*/
+ (BOOL)SystemSelector:(SEL)systemSelector swizzledSelector:(SEL)swizzledSelector error:(NSError *)error{
Method systemMethod = class_getInstanceMethod(self, systemSelector);
if (!systemMethod) {
return [[self class] unrecognizedSelector:systemSelector error:error];
}
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
if (!swizzledMethod) {
return [[self class] unrecognizedSelector:swizzledSelector error:error];
}
if (class_addMethod([self class], systemSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
method_exchangeImplementations(systemMethod, swizzledMethod);
}
return YES;
}
+ (BOOL)unrecognizedSelector:(SEL)selector error:(NSError *)error{
NSString *errorString = [NSString stringWithFormat:@"%@类没有找到%@", NSStringFromClass([self class]), NSStringFromSelector(selector)];
error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1userInfo:@{NSLocalizedDescriptionKey:errorString}];
return NO;
}
@end然后,给 NSMutableArray 添加分类(ErrorHandle),在.m文件中只需要写下如下代码:
#import "NSMutableArray+ErrorHandle.h"
#import "NSObject+SwizzleMethod.h"
#import <objc/runtime.h>
@implementation NSMutableArray (ErrorHandle)
+(void)load{
[super load];
//无论怎样 都要保证方法只交换一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//交换NSMutableArray中的方法
[objc_getClass("__NSArrayM") SystemSelector:@selector(objectAtIndex:) swizzledSelector:@selector(jz_objectAtIndex:) error:nil];
//交换NSMutableArray中的方法
[objc_getClass("__NSArrayM") SystemSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(jz_objectAtIndexedSubscript:) error:nil];
});
}
- (id)jz_objectAtIndex:(NSUInteger)index{
if (index < self.count) {
return [self jz_objectAtIndex:index];
}else{
NSLog(@" 你的NSMutableArray数组已经越界 帮你处理好了%ld %ld %@", index, self.count, [self class]);
return nil;
}
}
- (id)jz_objectAtIndexedSubscript:(NSUInteger)index{
if (index < self.count) {
return [self jz_objectAtIndexedSubscript:index];
}else{
NSLog(@" 你的NSMutableArray数组已经越界 帮你处理好了%ld %ld %@", index, self.count, [self class]);
return nil;
}
}
@end
0条评论