Method Swizzling

Method Swizzling,又称动态方法交换,本质上是通过交换SEL和IMP的对应关系,从而实现实现方法的互换。

在OC中,每一个Method类的对象都对应了一个方法。每一个Method的实例对应的都有一个objc_method的结构体:

1
2
3
4
5
6
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name
char * _Nullable method_types
IMP _Nonnull method_imp
}

其中method_name表示的就是方法名,method_types表示的是方法类型,而method_imp表示的是方法的地址,因此Method Swizzling就是通过交换method_imp来实现方法的交换。

实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// 如果要交换类方法,则使用
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(originalFunc);
SEL swizzledSelector = @selector(swizzledFunc);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 如果当前类没有原方法的IMP,说明在从父类继承过来的方法实现,
// 需要在当前类中添加一个originalSelector 方法,
// 但是用替换方法 swizzledMethod 去实现它
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
// 原方法的IMP 添加成功后,修改替换方法的IMP为原始方法的IMP
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void) swizzledFunc
{
[self swizzledFunc];
}

注意点

  1. 方法的交换应该放在load中进行,而不应该放在init中,因为不管你是否初始化对象,OC总是会调用load函数,而且不需要调用[super load]以避免发生多次交换;

  2. 需要把交换放在dispatch_once中,这样可以始终保证只交换一次;

  3. 在新的函数实现中仍需要调用[self swizzledFunc],注意这里并不会产生死循环,因为进入到这个方法内部的时候,实际上调用的是originalFunc对应的函数实现,也就是相当于调用了原来函数的实现,然后就可以在这个实现的上下文中添加自己想要的实现代码;

  4. 可能产生由于子类未实现父类的函数导致的父类的函数方法被篡改的情况;

  5. 注意不要产生命名冲突。

更好的实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 这里的id都可以改成对应的类型,或者在函数内部强转
// args1后面可以拓展任意个数的参数
void (*MethodOriginal)(id self, SEL _cmd, id arg1, ...);
void MethodSwizzle(id self, SEL _cmd, id arg1, ...) {
// 自己想要插入的代码
MethodOriginal(self, _cmd, arg1, ...);
}
BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMP *store)
{
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);
if (method) {
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);
if(!imp){
imp = method_getImplementation(method);
}
}
// 如果imp不存在,则说明当前函数是继承的父类,因此不进行imp的存储
if (imp && store) *store = imp;
return (imp != NULL);
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzle:@selector(viewDidAppear:) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
});
}
+ (BOOL)swizzle: (SEL) original with: (IMP) replacement store: (IMP *) store
{
return class_swizzleMethodAndStore(self, original, replacement, store);
}

FishHook

FishHook主要用于hook系统的函数,可以用来修改method_exchangeImplementations函数从而防止别人hook我们的函数。

  1. 首先要下载并引入fishhook.c和fishhook.c

fishhook

  1. 编写hook函数,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
static void (*orig_nslog)(NSString *, ...);
void myNSLog(NSString *str, ...){
va_list ap;
va_start(ap, str);
NSString *result = [[NSString alloc] initWithFormat:str arguments:ap];
orig_nslog([result stringByAppendingFormat:@" Hooked!"]);
va_end(ap);
}
- (void)viewDidLoad {
[super viewDidLoad];
rebind_symbols((struct rebinding[1]){{"NSLog", myNSLog, (void *)&orig_nslog}}, 1);
NSLog(@"WWWWW %d", 3); // 输出结果为WWWWW 3 Hooked!
}